<?php
/**
 * @package         Better Preview
 * @version         6.1.0
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2018 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Plugin\System\BetterPreview\Component;

defined('_JEXEC') or die;

use JFactory;
use JHtml;
use JText;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;
use RegularLabs\Plugin\System\BetterPreview\Params;

class Preview extends Helper
{
	var $states = [];
	var $errors = [];

	function render(&$article, $context)
	{
		JHtml::_('jquery.framework');

		RL_Document::script('betterpreview/preview.min.js', '6.1.0');
		RL_Document::style('betterpreview/preview.min.css', '6.1.0');

		$has_changes = 0;
		$data        = JFactory::getApplication()->input->get('previewdata', [], 'array');
		$this->urlDecode($data);

		$textpre = $this->getText($article);

		if (isset($data['attribs']) && ! isset($data['params']))
		{
			$data['params'] = $data['attribs'];
		}

		// Set created_by_alias when created_by is set to other user
		if (
			empty($data['created_by_alias'])
			&& ! empty($data['created_by'])
			&& ! empty($article->created_by)
			&& $data['created_by'] != $article->created_by
		)
		{
			$data['created_by_alias'] = JFactory::getUser($data['created_by'])->name;
		}

		// Boy, I hate this code with nested ifs.
		// This needs to get refactored!
		foreach ($data as $key => $val)
		{
			// ignore nonexistent fields
			if ( ! isset($article->{$key}))
			{
				continue;
			}

			// ignore crappy tags
			// and dynamic/unsettable fields
			// and date fields (because offset checking is a hell)
			if (in_array($key, [
				'tags',
				'published', 'state', 'access', 'hits', 'version',
				'created', 'modified', 'publish_up', 'publish_down',
			]))
			{
				continue;
			}

			$objects = ['params', 'attribs', 'metadata', 'urls', 'images'];

			if (in_array($key, $objects))
			{
				if (is_object($article->{$key}))
				{
					foreach ($val as $k => $v)
					{
						if (is_string($v) && $v == '')
						{
							continue;
						}

						if ( ! $has_changes && $this->diff($article->{$key}->get($k), $v))
						{
							$has_changes = 1;
						}

						$article->{$key}->set($k, $v);
					}

					continue;
				}

				if (is_string($article->{$key}))
				{
					$obj = (object) json_decode($article->{$key});
					if (is_object($obj))
					{
						// force fields to null if originals are.
						foreach ($val as $k => $v)
						{
							$this->prepareValueByKey($v, $k, $obj);
							if ( ! isset($v))
							{
								unset($val[$k]);
								continue;
							}

							$val[$k] = $v;
						}

						$val = urldecode(json_encode($val));
					}
				}
			}
			else
			{
				$this->prepareValueByKey($val, $key, $article);
			}

			if ( ! in_array($key, ['introtext', 'fulltext', 'text']))
			{
				if ( ! $has_changes && $this->diff($article->{$key}, $val))
				{
					$has_changes = 1;
				}
			}

			$article->{$key} = $val;
		}

		$article->text = $this->getText($article);

		// Fix weird issue with the video attribute being filled with the text on K2 items
		// @todo: find out why that happens
		if ($context = 'com_k2.item' && isset($article->video) && $article->video == $article->text)
		{
			// Set video value to data value or blank
			$article->video = isset($data['video']) ? $data['video'] : '';
		}

		if ( ! $has_changes && $this->diff($article->text, $textpre))
		{
			$has_changes = 1;
		}

		if ($has_changes)
		{
			$this->errors['BP_MESSAGE_HAS_CHANGES'] = JText::_('BP_MESSAGE_HAS_CHANGES');
		}
	}

	function purgeCache()
	{
		$params = Params::get();

		if ( ! $params->purge_component_cache
			|| ! $option = JFactory::getApplication()->input->get('option')
		)
		{
			return;
		}

		try
		{
			JFactory::getCache($option)->clean();
		}
		catch (Exception $e)
		{
			// ignore
		}
	}

	function setLanguage()
	{
		$language          = JFactory::getLanguage();
		$previous_language = JFactory::getApplication()->input->get('lang');

		if ($language->get('lang') != $previous_language && $language->exists($previous_language))
		{
			$language->setLanguage($previous_language);
		}
	}

	function getText(&$article)
	{
		if ($this->getShowIntro($article) && isset($article->introtext) && isset($article->fulltext))
		{
			return $article->introtext . ' ' . $article->fulltext;
		}

		if ( ! empty($article->fulltext))
		{
			return $article->fulltext;
		}

		if ( ! empty($article->introtext))
		{
			return $article->introtext;
		}

		return $article->text;
	}

	function urlDecode(&$data)
	{
		if (is_array($data) || is_object($data))
		{
			foreach ($data as $k => $v)
			{
				$this->urlDecode($data[$k]);
			}
		}
		else if (is_string($data))
		{
			$data = urldecode($data);
		}
	}

	function prepareValueByKey(&$val, $key, &$obj)
	{
		if (is_array($val) && $val == [0])
		{
			unset($val);

			return;
		}

		if (property_exists($obj, $key))
		{
			$v = $obj->{$key};
			switch ($v)
			{
				case null:
					if (empty($val))
					{
						$val = $v;
					}
					break;
				case '0000-00-00 00:00:00':
					if ($val == '')
					{
						$val = $v;
					}
					break;
			}
			if (is_bool($v))
			{
				$val = $val ? true : false;
			}
		}
	}

	function diff($string_1, $string_2)
	{
		if (is_string($string_1) && ! is_string($string_2))
		{
			if ( ! is_array($string_2))
			{
				return true;
			}

			$string_1 = explode(',', $string_1);
		}

		if (is_array($string_1) && ! is_array($string_2))
		{
			return true;
		}

		if (is_object($string_1) && ! is_object($string_2))
		{
			return true;
		}

		if (is_array($string_2) || is_object($string_2))
		{
			foreach ($string_2 as $k => $v)
			{
				if ( ! $this->diff($string_1[$k], $v))
				{
					continue;
				}

				return true;
			}

			return false;
		}

		if ( ! is_string($string_2))
		{
			return false;
		}

		$this->prepareString($string_1);
		$this->prepareString($string_2);

		return ($string_1 != $string_2);
	}

	private function prepareString(&$string)
	{
		if ( ! is_string($string))
		{
			return;
		}

		$string = trim(RL_RegEx::replace('\s', '', $string));
		$string = RL_String::html_entity_decoder($string);
	}

	function addMessages()
	{
		$html = JFactory::getApplication()->getBody();

		if ($html == '')
		{
			return;
		}

		// Set the category description if original description is empty
		// Need to do this because the onContentPrepare is not triggered when the description is empty
		if (JFactory::getApplication()->input->get('view') == 'category')
		{
			$empty_cat = '(<div class="category-desc[^"]*">)\s*(</div>)';
			if (RL_RegEx::match($empty_cat, $html))
			{
				$data = JFactory::getApplication()->input->get('previewdata', [], 'array');
				$this->urlDecode($data);
				if (isset($data['description']))
				{
					$html = RL_RegEx::replace($empty_cat, '\1' . $data['description'] . '\2', $html);
				}
			}
		}

		$html = str_replace('</body>', '<div class="betterpreview_message">' . JText::_('BP_MESSAGE_PAGE') . '</div></body>', $html);

		if ( ! empty($this->errors))
		{
			$html = str_replace('</body>', '<div class="betterpreview_error">' . implode('<br>', $this->errors) . '</div></body>', $html);
		}

		JFactory::getApplication()->setBody($html);
	}

	public function states()
	{
	}

	function initStates($item_name = 'content', $item_states = [], $parent_name = 'categories', $parent_states = [])
	{
		$this->getState(
			JFactory::getApplication()->input->get('id'),
			$item_name,
			$item_states
		);

		$item = $this->states[count($this->states) - 1];

		while ($item->parent != 0)
		{
			$this->getState(
				$item->parent,
				$parent_name,
				$parent_states,
				true
			);

			$item = $this->states[count($this->states) - 1];
		}

		$this->setStates();
	}

	function getShowIntro(&$article)
	{
		if ($article && isset($article->params))
		{
			return $article->params->get('show_intro', '1');
		}

		return true;
	}

	function getState($id, $table, $names = [], $isparent = false)
	{
		$names = (object) array_merge(
			[
				'id'        => 'id',
				'published' => 'published',
				'access'    => 'access',
				'parent'    => 'parent',
			], $names
		);

		$data = JFactory::getApplication()->input->get('previewdata', [], 'array');
		$this->urlDecode($data);

		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->from('#__' . $table . ' as a')
			->where('a.' . $names->id . ' = ' . (int) $id);

		foreach ($names as $k => $v)
		{
			if ( ! $k || ! $v)
			{
				continue;
			}

			$query->select('a.' . $v . ' as ' . $k);
		}

		$db->setQuery($query);
		$item = $db->loadObject();

		if ( ! $item)
		{
			return;
		}

		$state = (object) [
			'table'     => $table,
			'id'        => $id,
			'published' => $item->published,
			'access'    => $item->access,
			'parent'    => $item->parent,
			'names'     => $names,
		];

		if (isset($names->publish_up) && $item->publish_up > 0)
		{
			$state->publish_up = $item->publish_up;
		}

		if (isset($names->publish_down) && $item->publish_down > 0)
		{
			$state->publish_down = $item->publish_down;
		}

		if (isset($names->hits))
		{
			$state->hits = $item->hits;
		}

		$this->states[] = $state;

		if ( ! empty($this->errors))
		{
			return;
		}

		$now = strtotime(JFactory::getDate()->format('Y-m-d H:i:s'));

		if (
			$item->published != 1
			|| (isset($data['published']) && $data['published'] != 1)
			|| (isset($item->publish_up) && $item->publish_up > 1 && strtotime($item->publish_up) > $now)
			|| (isset($data['publish_up']) && $data['publish_up'] > 1 && strtotime($data['publish_up']) > $now)
			|| (isset($item->publish_down) && $item->publish_down > 1 && strtotime($item->publish_down) < $now)
			|| (isset($data['publish_down']) && $data['publish_down'] > 1 && strtotime($data['publish_down']) < $now)
		)
		{
			$this->errors['BP_MESSAGE'] = $isparent ? JText::_('BP_MESSAGE_PARENT_UNPUBLISHED') : JText::_('BP_MESSAGE_ITEM_UNPUBLISHED');

			return;
		}

		if ($item->access != 1 || (isset($data['access']) && $data['access'] != 1))
		{
			$this->errors['BP_MESSAGE'] = $isparent ? JText::_('BP_MESSAGE_PARENT_ACCESS') : JText::_('BP_MESSAGE_ITEM_ACCESS');

			return;
		}
	}

	function setStates()
	{
		foreach ($this->states as $state)
		{
			$this->setState($state);
		}
	}

	function setState($state)
	{
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->update('#__' . $state->table)
			->where($db->quoteName($state->names->id) . ' = ' . $db->quote($state->id));

		if (isset($state->names->published))
		{
			$query->set($db->quoteName($state->names->published) . ' = 1');
		}

		if (isset($state->names->access))
		{
			$query->set($db->quoteName($state->names->access) . ' = 1');
		}

		if (
			isset($state->names->publish_up)
			&& isset($state->publish_up)
			&& $state->publish_up > 0
		)
		{
			$query->set($db->quoteName($state->names->publish_up) . ' = DATE_SUB(CURDATE(), INTERVAL 2 DAY)');
		}

		if (
			isset($state->names->publish_down)
			&& isset($state->publish_down)
			&& $state->publish_down > 0
		)
		{
			$query->set($db->quoteName($state->names->publish_down) . ' = DATE_ADD(CURDATE(), INTERVAL 2 DAY)');
		}

		$db->setQuery($query);
		$db->execute();
	}

	function restoreStates()
	{
		foreach ($this->states as $state)
		{
			$this->restoreState($state);
		}
	}

	function restoreState($state)
	{
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->update('#__' . $state->table)
			->where($db->quoteName($state->names->id) . ' = ' . $db->quote($state->id));

		if (isset($state->names->published))
		{
			$query->set($db->quoteName($state->names->published) . ' = ' . $state->published);
		}

		if (isset($state->names->access))
		{
			$query->set($db->quoteName($state->names->access) . ' = ' . $state->access);
		}

		if (isset($state->names->hits))
		{
			$query->set($db->quoteName($state->names->hits) . ' = ' . $state->hits);
		}

		if (
			isset($state->names->publish_up)
			&& isset($state->publish_up)
			&& $state->publish_up > 0
		)
		{
			$query->set($db->quoteName($state->names->publish_up) . ' = ' . $db->quote($state->publish_up));
		}

		if (
			isset($state->names->publish_down)
			&& isset($state->publish_down)
			&& $state->publish_down > 0
		)
		{
			$query->set($db->quoteName($state->names->publish_down) . ' = ' . $db->quote($state->publish_down));
		}

		$db->setQuery($query);
		$db->execute();
	}

	function checkSession()
	{
		$session_id = JFactory::getApplication()->input->get('session_id');
		$user_id    = JFactory::getApplication()->input->get('user');
		$client_id  = JFactory::getConfig()->get('shared_session', '0') ? 0 : 1;

		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('session_id'))
			->from($db->quoteName('#__session'))
			->where($db->quoteName('userid') . ' = ' . $db->quote($user_id))
			->where($db->quoteName('session_id') . ' = ' . $db->quote($session_id))
			->where($db->quoteName('client_id') . ' = ' . $client_id)
			->order($db->quoteName('time') . ' DESC');
		$db->setQuery($query);
		$result = (string) $db->loadResult();

		return ($result && $result == $session_id);
	}
}