<?php

class FrontController extends AppFrontController {

	var $page = null;
	var $lists = [];
	var $layout = false;
	public $components = [ 'Seo.Seo' ];

	var $assets = [ 'stylesheets' => [], 'javascripts' => [] ];

	var $elements = [];

	/** @inheritdoc No model. */
	public $uses = false;

	/** @var array The data that has been preloaded in the controller. */
	public $preloadedData = [];

	function index() {
	}

	function beforeFilter() {
		try {

			# If not installed
			if (!INSTALLED) {
				return $this->outputDefault();
			}

			# Check if url is redirected
			$this->checkRedirection();

			# Build the environment
			parent::beforeFilter();

			# If no front
			if (!Configure::read('Website.enabled') && empty($this->request->params)) {
				header('Location: ' . BACK_BASE_URL);
				exit;
			}

			# Authorize
			$this->Auth->allow();
			$this->request->params['get'] = $_GET;

			# Get the cookies
			$this->cookies = $this->Cookie->read();
			$this->set('_cookies', $this->cookies);

			# Get path
			$path = $this->request->params['pass'];

			# Read the page
			$page = $this->read($path);
			$this->assets($this->page);

			# If no page has been found, show under construction
			if (empty($this->sitemap)) {
				return $this->outputDefault();
			}

			# Parse all cms tags
			$this->handlePageParsing();

			# Called before lists
			if (method_exists($this, 'beforeLists')) {
				$this->beforeLists();
			}

			# Read all lists #
			foreach ($this->lists as $plural => $params) {
				if (!empty($params['module'])) {
					if (!isset($this->pageLinks[$params['module']])) {
						$this->pageLinks[$params['module']] = $this->Page->getPageLink($params['module']);
					}
					$limit = !empty($params['limit']) && is_numeric($params['limit']) ? [ 'limit' => $params['limit'] ] : [];
					$offset = !empty($params['offset']) && is_numeric($params['offset']) ? [ 'offset' => $params['offset'] ] : [];
					$order = !empty($params['order']) && preg_match('~^ *(\s*,?\s*`?[a-z0-9_]+`?.`?[a-z0-9_]+`? +(asc|desc)|rand\(\))+ *$~ i', $params['order']) ? [ 'order' => $params['order'] ] : [];

					# Conditions
					$conditions = isset($this->page['Conditions'][$params['module']]) ? $this->page['Conditions'][$params['module']] : [];
					if (!empty($params['filter'])) {
						//!!$conditions[] = mysql_escape_string($params['filter']);
						$conditions[] = str_replace('_gt', '>', $params['filter']);
					}

					if (isset($params['additional_conditions'])) {
						$conditions = array_merge($conditions, $params['additional_conditions']);
					}

					# Use current module
					$this->Module = $this->{$params['module']};

					# Check if module exists
					if (empty($this->Module)) {
						throw new Exception("Module '{$params['module']}' could not have been found.");
					}

					// Get request conditions
					$additionalConditions = $this->Module->getRequestConditions($this->request);
					if (!$additionalConditions) {
						$additionalConditions = [];
					}
					$conditions = array_merge($conditions, $additionalConditions);

					// Set paginationless url
					$paginationlessUrl = $this->request->here;
					if (preg_match('~(.*)/page:[0-9]+~', $this->request->here, $matches)) {
						$paginationlessUrl = $matches[1];
					}
					$this->set(compact('paginationlessUrl'));

					# is_active
					if ($this->Module->schema('is_active')) {
						$conditions["{$this->Module->alias}.is_active"] = true;
					}
					# is_deleted
					if ($this->Module->schema('is_deleted')) {
						$conditions["{$this->Module->alias}.is_deleted"] = false;
					}
					# is_translated
					if ($this->Module->schema('is_translated')) {
						$conditions["{$this->Module->alias}.is_translated"] = true;
					}
					# approved
					if ($this->Module->schema('approval_status')) {
						$conditions["{$this->Module->alias}.approval_status"] = 'Accepted';
					}

					# Page
					# Paginator baguje kada mu se proslede uslovi vezani za asocijacije. Na ovaj nacin se to izbegava
					if (!isset($params['anypage']) && $this->Module->schema('page_id') && !in_array("{$this->Module->alias}.page_id", $conditions, true)) {
						if (isset($this->page['Page']['lft']) && isset($this->page['Page']['rght'])) {
							$childPages = $this->Page->find('all', [
								'fields'     => [ 'id' ],
								'conditions' => [
									'Page.lft >=' => $this->page['Page']['lft'],
									'Page.lft <=' => $this->page['Page']['rght']
								]
							]);

							# If filter is empty
							if (empty($params['filter'])) {
								$allowedIds = [ 0 ];
								foreach ($childPages as $child) {
									$allowedIds[] = $child['Page']['id'];
								}
								$conditions["{$this->Module->alias}.page_id"] = $allowedIds;
							}
						}
					}

					$conditions = [ 'conditions' => $conditions ];

					# Complete query
					$query = $order + $limit + $offset + $conditions;

					# Pagination parameters
					$query = $this->Module->alterPaginationParams($query, $this->request);
					if (!empty($params['paginate'])) {
						$query['limit'] = (int) $params['paginate'];
						$this->paginate[$params['module']] = [ 'recursive' => 1 ] + $query;
						$this->set($plural, $this->paginate($params['module']));
					} else {
						$this->set($plural, $this->Module->find('all', $query));
					}
				}
			}
			$this->request->pageLinks = $this->pageLinks;

			$this->set('page', $this->page);
			$this->set('assets', $this->assets);

			# Meta tags
			$this->MetaTag = ClassRegistry::init('Seo.MetaTag');
			$tagDescriptions = $this->MetaTag->getAllById();
			$this->Seo->setTagDescriptions($tagDescriptions);
			$metaTags = $this->Seo->getMetaTags($this->page, $this->request);
			$forceTitle = $this->Seo->getPageTitle($this->page, $this->request);
			$seoConfig = $this->Seo->getConfig();
			$this->set(compact('metaTags', 'tagDescriptions', 'seoConfig', 'forceTitle'));

			$this->layout = false;

			$html = $this->page['Layout']['body'];

			# Translate all what is needed
			$html = $this->Dictionary->replace($html);

			# Write the last render
			if (Configure::read('debug')) {
				file_put_contents(TMP . 'last_render.php', $html);
			}

			# Handle custom logic after the page has been read from the database
			$this->afterPage();

			$this->set('html', $html);
			if (get_class($this) === 'FrontController') {
				$this->render('/Layouts/front');
			}

		} catch (NotFoundException $ex) {
			$this->redirect('/404');
		}
	}

	# ~ Handle cms tags parsing- - - - - - - - - - - - - - - - - - - - - - - - - - #
	function handlePageParsing() {

		Timer::start('Parser');
		{
			$cached = false;

			# Generate hash for cache file
			if (Configure::read('Feature.advanced_caching')) {
				# Define hash for caching
				$hash = 'Page_' . $this->page['Page']['id'];
				# Add module if present
				if (!empty($this->page['Page']['module_id'])) {
					$hash .= '_Module:' . $this->page['Page']['module_id'] . '_' . strtotime($this->page['Page']['modified']);
				} else {
					$hash .= '_' . md5(json_encode($this->page));
				}

				# Add current language to hash
				$hash .= '_' . Configure::read('Config.Language');

				$cacheFile = CACHE . 'pages' . DS . $hash;

				# Use advanced caching if file exists and debug is off
				if (Configure::read('debug') == 0 && file_exists($cacheFile)) {
					$cacheContents = file_get_contents($cacheFile);
					$cached = @json_decode($cacheContents, true);
				}
			}

			$block = null;

			if (!empty($cached)) {
				# Use cached value if present
				$this->page['Layout']['body'] = $cached['body'];
				$this->lists = $cached['lists'];
				$this->elements = $cached['elements'];
				$block = $cached['block'];
			} else {
				# Parse All
				$this->elements = $this->parse($this->page['Layout']['body'], $this->elements);
				$this->ec($this->page['Layout']['body'], $this->elements);

				# Open graph
				$block = $this->page['Page']['meta_description'];
				if (!empty($this->page['Block'][0])) {
					foreach ($this->page['Block'] as $block) {
						if ($block['language'] == Configure::read('Config.language')) {
							$block = $block['content'];
							$elements = $this->parse($this->page['Block'][0]['content'], []);
							$this->ec($block, $elements);
							break;
						}
					}
				}

				# Clean up #
				Timer::start('Cleanup');
				{
					$this->page['Layout']['body'] = @$this->layout($this->page['Layout']['body']);
					$lines = explode("\n", $this->page['Layout']['body']);
					foreach ($lines as $i => $line) {
						$lines[$i] = ($i + 1) . "\t" . $line;
					}
				}
				Timer::end('Cleanup');

				if (Configure::read('Feature.advanced_caching')) {
					$cacheData = [
						'body'     => $this->page['Layout']['body'],
						'lists'    => $this->lists,
						'elements' => $this->elements,
						'block'    => $block
					];
					file_put_contents($cacheFile, json_encode($cacheData));
				}
			}
		}
		Timer::end('Parser');
	}

	public function beforeRender() {
		$this->set('preloadedData', $this->preloadedData);
	}

	# ~ Read the page, based on the supplied path  - - - - - - - - - - - - - - - - #
	function read($path = null) {
		$page = $this->Page->fetch($path);

		if (is_string($page)) {
			return $this->redirect($page, 301);
		}

		# Module title
		$this->set('pageTitleAppend');
		if (!empty($page['Page']['type']) && !empty($page['Page']['title']) && $page['Page']['type'] === 'Module') {
			$this->set('pageTitleAppend', $page['Page']['title']);
		}

		# Redirect wrappers
		if ($page['Page']['type'] == 'Wrapper') {
			$id = $this->Page->field('id', [ 'parent_id' => $page['Page']['id'] ]);
			if ($id) {
				$this->redirect($this->Page->getPageLink($id));
			}
		}

		# No page found
		if (empty($page['Page'])) {
			return null;
		}

		# Get assets
		$query = [
			'fields'    => [
				'id', 'name', 'data', 'modified' ],
			'order'     => [
				'id' => DESC ],
			'recursive' => -1
		];
		foreach ([ 'Stylesheet', 'Javascript' ] as $asset) {
			$small = Inflector::underscore($asset);
			$field = $small . '_set';
			if (!empty($page['Layout'][$field])) {
				$list = mergeSets($page['Layout'][$field], $page['Content'][$field]);
				$query['conditions'] = "{$asset}.id IN ({$list})";
				$assets = $this->Page->Layout->$asset->find('all', $query);
				foreach ($assets as $item) {
					$page[$asset][] = $item[$asset];
				}
			}
		}

		# Extract module variables
		if (!empty($page['Variables'])) {
			$this->set($page['Variables']);
		}

		# Set page
		$this->page = $page;
		$this->set('activePage', $this->page);

		# Set sitemap
		$this->sitemap = $this->Page->sitemap;
		$this->flatSitemap = $this->Page->flatSitemap;
		$this->request->path = activePath($this->sitemap, 'Page.selected', 'Page.id');

		# Paste varibles to view
		$this->set([
			'here'        => $this->request->here,
			'sitemap'     => $this->sitemap,
			'flatSitemap' => $this->flatSitemap
		]);

		return $this->page;
	}

	function assets($assets) {
		if (!empty($assets['Stylesheet'])) {
			foreach ($assets['Stylesheet'] as $stylesheet) {
				$this->assets['stylesheets'][$stylesheet['id']] = $stylesheet;
			}
		}
		if (!empty($assets['Javascript'])) {
			foreach ($assets['Javascript'] as $javascript) {
				$this->assets['javascripts'][$javascript['id']] = $javascript;
			}
		}
	}

	function layout($data, $tab = "\t") {
		return $data;
		$ln = false;
		$tag = true;
		$skip = false;
		$level = 0;
		$return = '';
		$data = str_replace([ "\t", "\n", "\r" ], [ ' ', ' ', ' ' ], $data);
		$data = trim(preg_replace('/ +/', ' ', $data));

		for ($i = 0; isset($data{$i}); $i++) {
			$char = $data{$i};
			$prev = isset($data{$i - 1}) ? $data{$i - 1} : null;
			$next = isset($data{$i + 1}) ? $data{$i + 1} : null;
			if ($data{$i} == '<' && !$skip) {

				if ($next == '?') {
					$skip = true;
					$return .= $char;
					continue;
				}

				if (!$tag = $next != '/') {
					$ln = "\n" . str_repeat($tab, --$level);
				}
				$char = $ln . $char;
			} else if ($data{$i} == '>') {
				$ln = "\n" . str_repeat($tab, $level);
				if (!$skip) {
					if ($prev != '/') {
						$level += $tag;
						$char .= "\n" . str_repeat($tab, $level);
					}
					$ln = false;
				} else if ($prev == '?') {
					$char .= $next == '<' ? "\n" : '';
					$skip = false;
				}
			}
			$return .= substr($return, -1) == "\t" ? trim($char, ' ') : $char;
		}
		return $return;
	}

	function parse($data, $elements) {

		# Get all elements
		$elementList = listPublicFiles('elements', 'php', false);

		# Exract dynamic CMS tags
		$matches = $this->Page->parseDynamicTags($data);

		# Iterate trough all params found
		if (!empty($matches)) {
			foreach ($matches as $i => $match) {
				extract($match);

				# Extract true HTML attributes
				$html = '';
				$attrs = Set::normalize([ 'id', 'alt', 'rel', 'class', 'style', 'target' ]);
				$attrs = array_intersect_key($attributes, $attrs);
				foreach ($attrs as $key => $value) {
					if (!empty($value)) {
						$html .= " {$key}=\"{$value}\"";
						unset($attributes[$key]);
					}
				}

				# For each type
				switch ($type) {

					# element
					case 'element':
						$element = ClassRegistry::init('Element')->read([ 'id', 'body', 'stylesheet_set', 'javascript_set' ], (int) $attrs['id']);

						if (!$element)
							throw new Exception("Element number `{$attrs['id']}` not found.");
						$replace = $content = $element['Element']['body'];
						$this->page['Layout']['body'] = str_replace($match, $replace, $this->page['Layout']['body']);
						break;

					# block
					case 'block':
						$replace = $this->page['Page']['content'];
						$content = preg_replace('~cms\s*=\s*"block"~', '', $replace);
						$this->page['Layout']['body'] = str_replace($match, $replace, $this->page['Layout']['body']);
						break;

					# image
					case 'image':
						$plugin = isset($attributes['plugin']) ? (string) $attributes['plugin'] : 'match';
						$width = !empty($attributes['width']) && (int) $attributes['width'] ? $attributes['width'] : 'false';
						$height = !empty($attributes['height']) && (int) $attributes['height'] ? $attributes['height'] : 'false';
						$fancybox = !empty($attributes['fancybox']) ? '"' . $attributes['fancybox'] . '"' : 'false';

						$source = $this->source(!empty($attributes['src']) ? $attributes['src'] : $this->source($content));
						$additional = var_export($attrs, true);

						$replace = "<?= \$this->Manicure->{$plugin}({$source} + {$additional}, {$width}, {$height}, {$fancybox}) ?>";
						break;

					# flash
					case 'flash':
						$width = !empty($attributes['width']) && (int) $attributes['width'] ? $attributes['width'] : '210';
						$height = !empty($attributes['height']) && (int) $attributes['height'] ? $attributes['height'] : '210';

						$source = $this->source($content);

						$replace = "<?= \$this->Format->flash({$source}, {$width}, {$height}) ?>";
						break;

					# echo
					case 'echo':

						# Grab the source
						preg_match('~(?P<pre>(?:\s*\<[^\>]+\>\s*)+)?\s*(?<content>[^\<]+)?\s*(?P<post>.*)?~ is', $content, $temp);
						$source = parseSource($temp, !isset($attributes['format']));

						# Go through attributes
						if (!empty($attributes)) {

							# format text #
							if (!empty($attributes['format'])) {
								switch ($attributes['format']) {
									case 'html':
										$source = "text2html({$source})";
										break;
									case 'plain':
										$source = "html2text({$source})";
										break;
									default:
										$source = "\$this->Front->{$attributes['format']}({$source})";
								}
							}

							# truncate #
							if (!empty($attributes['truncate']) && (int) $attributes['truncate']) {
								$source = "\$this->Format->truncate({$source}, {$attributes['truncate']})";
							}

							# date format #
							if (!empty($attributes['timeformat'])) {
								$timeformat = str_replace("'", "\\'", $attributes['timeformat']);
								$source = "strftime('{$timeformat}', strtotime({$source}))";
							}
						}

						$replace = "<?= {$source} ?>";

						# Replace tag
						$replaceTag = !in_array($tag, [ 'code', 'img' ]) ? $tag : null;

						# Final repacking of result
						$replace = "{$temp['pre']}{$replace}{$temp['post']}";
						if ((!empty($replaceTag) || !empty($html)) && !isset($attributes['href'])) {

							# Default replace tag
							if (empty($replaceTag)) {
								$replaceTag = 'span';
							}

							$replace = "<{$replaceTag}{$html}>{$replace}</{$replaceTag}>";
						}
						break;

					# list
					case 'list':
						if (isset($attributes['module'])) {
							$modules = explode('.', $attributes['module']);

							# Set the alias
							$last = end($modules);
							$module = array_shift($modules);
							$alias = !empty($attributes['alias']) ? $attributes['alias'] : $module;

							# Set the variables
							$plural = !empty($attributes['plural']) ? $attributes['plural'] : Inflector::variable(Inflector::pluralize($alias));
							$single = !empty($attributes['single']) ? $attributes['single'] : Inflector::variable(Inflector::singularize($last));

							# Build source path
							$source = '';
							foreach ($modules as $module) {
								$source .= "['{$module}']";
							}
							$source = sizeof($modules) ? Inflector::singularize($plural) . $source : $plural . $source;

							while (isset($this->lists[$source]))
								$source .= '_';
							$content = str_replace($source, $single, $matches[$i]['content']);

							if (empty($modules)) {
								$this->lists[$source] = $attributes;
							}

							# Apply provided class to li
							$liClass = '';
							if (!empty($attributes['itemclass'])) {
								$liClass = "\$class .= ' {$attributes['itemclass']}';";
							}

							# Apply modulus class to li
							$modulus = '';
							if (!empty($attributes['modulus'])) {
								$modulus = "\$class .= \$i%{$attributes['modulus']} ? '' : ' modulus';";
							}

							# Always add cms-list class
							if (strpos($html, 'class="') !== false) {
								$html = str_replace('class="', 'class="cms-list ', $html);
							} else {
								$html .= ' class="cms-list"';
							}

							# Parse variables as well
							$content = preg_replace('~{(\$[a-z0-9_]+([\'[a-z0-9_]+\'])+)}~ i', '<?= $1 ?>', $content);

							# Tag for list
							$listTag = "ul";
							if (!empty($attributes['list'])) {
								$listTag = $attributes['list'];
							} else if ($tag !== 'ul' && $tag !== 'code') {
								$listTag = $tag;
							}

							# Tag for item
							if (!empty($attributes['item'])) {
								$itemTag = $attributes['item'];
							} else if ($listTag != 'ul') {
								$itemTag = $listTag;
							} else {
								$itemTag = 'li';
							}

							# Create replace string
							// TODO: Vidi, majke ti, da smiliš nešto lepše :(
							$replace = "
								<{$listTag}{$html}>
									<?php
										\$tempSource = \${$source};
										\$tmp = isset(\${$single}) ? \${$single} : null;

										foreach(\$tempSource as \$i => \${$single}) {
											\$class = 'cms-list-item';
											{$liClass}
											if(!\$i) \$class .= ' first';
											if(!isset(\$tempSource[\$i+1])) \$class .= ' last';
											{$modulus}
											?>
<{$itemTag} class=\"<?= \$class ?>\">
	{$content}
</{$itemTag}>
											<?php
										}
										\${$single} = \$tmp;
									?>
								</{$listTag}>";
						}
						break;

					# paginator
					case 'paginator':
						$options = [
							'separator' => isset($attribute['separator']) ? $attribute['separator'] : ''
						];

						$prev = isset($attributes['prev']) ? $attributes['prev'] : '«';
						$next = isset($attributes['next']) ? $attributes['next'] : '»';
						$prev = !empty($prev) ? "(\$this->Paginator->hasPrev() ? \$this->Paginator->prev('{$prev}', array('tag' => 'span')) : '')" : "''";
						$next = !empty($next) ? "(\$this->Paginator->hasNext() ? \$this->Paginator->next('{$next}', array('tag' => 'span')) : '')" : "''";

						$attributes['url'] = $this->passedArgs;
						$options = var_export($options, true);
						$replace = "<div class=\"paginator\" {$html}><?= {$prev} . \$this->Paginator->numbers({$options}) . {$next} ?></div>";
						break;

					# comments
					case 'comments':
						$element = isset($attributes['element']) ? $attributes['element'] : '1';
						$replace = "<?= \$this->element('comments/{$element}-comments') ?>";
						break;

					# unknown tag
					default:
						throw new CakeException("Unknown cms tag: {$type}");

				}

				# Apply anchor tag
				if (!empty($attributes['href'])) {

					# Module link
					if (ctype_alpha($attributes['href'])) {
						$module = Inflector::classify($attributes['href']);
						$variable = Inflector::singularize(Inflector::variable($module));

						//$attributes['href'] = "\$this->request->pageLinks['{$module}'] . \${$variable}['{$module}']['slug']";
						$attributes['href'] = "\$this->Format->getModulePath(\${$variable})";

						# Handle title field
						if (empty($attributes['title'])) {
							$attributes['title'] = "\${$variable}['{$this->{$module}->alias}']['{$this->{$module}->displayField}']";
						} else {
							$indexes = explode('.', $attributes['title']);
							$attributes['title'] = "\${$indexes[0]}";
							for ($i = 1; $i < count($indexes); $i++) {
								$attributes['title'] .= "['" . $indexes[$i] . "']";
							}
						}

						# URL from field
					} else if (preg_match('~^[a-z]+(.[a-z\.])+$~ i', $attributes['href'])) {
						$split = explode('.', $attributes['href'], 2);
						$nested = preg_replace('~\.~', "']['", $split[1]);

						$attributes['href'] = "\$this->Format->getPageLink(\${$split[0]}['{$nested}'], \$sitemap)";

						# Escape the litelars
					} else if ($attributes['href']{0} !== '$') {
						$attributes['href'] = "'{$attributes['href']}'";
					}

					$replace = "
						<?php if({$attributes['href']}) { ?>
							<a{$html} href=\"<?= \$this->Html->url({$attributes['href']}) ?>\" " . (!empty($attributes['title']) ? "title=\"<?= {$attributes['title']}?>\"" : "") . ">{$replace}</a>
						<?php } else { ?>
							{$replace}
						<?php } ?>";
				}

				# Set element
				$elements[$match] = $replace;
				initKey($elements, 'children', []);
				$elements['children'] = array_merge($elements['children'], $this->parse($content, $elements['children']));
			}
		}

		return $elements;
	}

	function source($content) {

		# Already parser
		if (isset($content{0}) && $content{0} === '$') {
			return $content;
		}

		# Literal value
		if (isset($content{0}) && $content{0} == '#') {
			return '"' . substr($content, 1) . '"';
		}

		$path = explode('.', $content);
		$source = '$' . Inflector::variable(array_shift($path));
		foreach ($path as $next) {
			$source .= "['{$next}']";
		}

		return $source;
	}

	function ec(&$body, $elements) {
		if (isset($elements['children'])) {
			$children = $elements['children'];
			unset($elements['children']);

			$this->ec($body, $children);

			foreach ($elements as $search => $replace) {
				unset($elements[$search]);
				unset($children['children']);
				$search = str_replace(array_keys($children), $children, $search);
				$replace = str_replace(array_keys($children), $children, $replace);
				$elements[$search] = $replace;
			}
		}

		$body = str_replace(array_keys($elements), $elements, $body);
	}

	# ~ Default page for front - - - - - - - - - - - - - - - - - - - - - - - - - - #
	public function outputDefault($title = [ 'Under construction', 'Vector CMS' ]) {
		exit(1);
	}

	# ~ Handle redirections- - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	private function checkRedirection() {
		$url = cleanUrl($this->request);

		# Find redirection
		$redirection = ClassRegistry::init('SeoRedirection')->find('first', [
			'conditions' => [
				'redirect_from' => $url
			]
		]);

		# If there is a redirection, redirect
		if (!empty($redirection)) {
			$redirectType = $redirection['SeoRedirection']['redirect_type'];
			$redirectType = substr($redirectType, 0, stripos($redirectType, '-'));
			$this->redirect($redirection['SeoRedirection']['redirect_to'], $redirectType);
			exit;
		}
	}

	protected function getPath() {
		return $this->request->params['pass'];
	}

}

