<?php

###############################################################################
#
# Класс с методами для проверки и исправления неиспользуемого системой
# диапазона цветов текущего скина. Проблема имеет место быть в скине Silver
# и остальных (в том числе и кастомных) сделанных на его основе, и заключается
# в переназначении почти всех цветов палитры на серый (0х797979), что вызывает
# проблемы как с правильным отображением цветных текстов в лейблах в плагинах,
# так и с низким контрастом серого текста на темном фоне. Уж не знаю какую
# цель преследовали создатели данного скина, по мне это определенно баг
# требующий исправления. Патч не затрагивает значения цветов из палитры
# используемых системой (с тем, чтобы не нарушить цветовой баланс в самом скине),
# но исправляет диапазон палитры неиспользуемый оболочкой, и который может быть
# использован разработчиками в своих плагинах.
#
# В неиспользуемый системой диапазон входят следующие цвета:
# DEF_LABEL_TEXT_COLOR_BLUE
# DEF_LABEL_TEXT_COLOR_PALEGREEN
# DEF_LABEL_TEXT_COLOR_LIGHTBLUE
# DEF_LABEL_TEXT_COLOR_LIMEGREEN
# DEF_LABEL_TEXT_COLOR_GOLD
# DEF_LABEL_TEXT_COLOR_VIOLET
# DEF_LABEL_TEXT_COLOR_TURQUOISE
# DEF_LABEL_TEXT_COLOR_ORANGE
# DEF_LABEL_TEXT_COLOR_MAGENTA
#
# Также исправляются цвета используемые системой для отображения текста в
# VOD-плагинах (при необходимости это можно легко отменить закомментировав
# соответствующие строки в массиве $dune_default_colors_values):
# DEF_LABEL_TEXT_COLOR_YELLOW
# DEF_LABEL_TEXT_COLOR_LIGHTGREEN
# DEF_LABEL_TEXT_COLOR_SKYBLUE
# DEF_LABEL_TEXT_COLOR_CORAL
# DEF_LABEL_TEXT_COLOR_DARKGRAY2
# DEF_LABEL_TEXT_COLOR_GAINSBORO
#
# Автор (Author): Brigadir (forum.mydune.ru)
# Дата (Date): 27-03-2017
# Последнее обновление (Latest update): 31-01-2021
#
###############################################################################

require_once 'lib/dune_stb_api.php';
require_once 'lib/action_factory.php';
require_once 'lib/control_factory.php';

///////////////////////////////////////////////////////////////////////////////

class DunePalettePatcher implements UserInputHandler
{

	///////////////////////////////////////////////////////////////////////////

	private static	$instance = null;
	private static	$checked = false;

	// Table of the range of colors not used by the DUNE GUI
	private static $dune_default_colors_values =
		array
			(
				DEF_LABEL_TEXT_COLOR_WHITE			=> 'ffffe0',
				DEF_LABEL_TEXT_COLOR_BLUE 			=> '0000a0',
				DEF_LABEL_TEXT_COLOR_PALEGREEN 		=> 'c0e0c0',
				DEF_LABEL_TEXT_COLOR_LIGHTBLUE 		=> 'a0c0ff',
				DEF_LABEL_TEXT_COLOR_LIMEGREEN 		=> 'c0ff40',
				DEF_LABEL_TEXT_COLOR_GOLD 			=> 'ffe040',
				DEF_LABEL_TEXT_COLOR_VIOLET 		=> '4040c0',
				DEF_LABEL_TEXT_COLOR_TURQUOISE 		=> '40ffff',
				DEF_LABEL_TEXT_COLOR_ORANGE 		=> 'ff8040',
				DEF_LABEL_TEXT_COLOR_MAGENTA 		=> 'ff40ff',
				DEF_LABEL_TEXT_COLOR_YELLOW 		=> 'ffff00',
				DEF_LABEL_TEXT_COLOR_LIGHTGREEN 	=> '50ff50',
				DEF_LABEL_TEXT_COLOR_SKYBLUE 		=> '5080ff',
				DEF_LABEL_TEXT_COLOR_CORAL 			=> 'ff5030',
				DEF_LABEL_TEXT_COLOR_DARKGRAY2 		=> '404040',
				DEF_LABEL_TEXT_COLOR_GAINSBORO 		=> 'e0e0e0',
			);

	///////////////////////////////////////////////////////////////////////////

    protected	$out_action;
    protected	$reboot_action;
	protected	$skin_path;

	///////////////////////////////////////////////////////////////////////////

	private static function copy_all($source, $target)
	{
		if (is_dir($source))
		{
			@mkdir($target);
			$dir = dir($source);

			while (($entry = $dir->read()) !== false)
			{
				if ($entry == '.' || $entry == '..')
					continue;

				self::copy_all("$source/$entry", "$target/$entry");
			}

			$dir->close();
		}
		else
			copy($source, $target);
	}

	///////////////////////////////////////////////////////////////////////////

	private function __construct()
	{
		UserInputHandlerRegistry::get_instance()->register_handler($this);
	}

	private function color_palette_check()
	{
		# Проверяет неиспользуемый системой диапазон цветовой палитры текущего скина
		# на соответствие с базовой (дефолтной), и возвращает истину если
		# расхождения не обнаружены, или фальш в противном случае.

		if (!file_exists($this->skin_path . '/dune_skin_config.xml'))
			return true;

		$dom = new DomDocument();
		$dom->load($this->skin_path . '/dune_skin_config.xml');
		$color = $dom->getElementsByTagName('color');
		$result = true;

		foreach ($color as $item)
		{
			$color_index = null;
			$color_value = null;
			foreach ($item->attributes as $attrName => $attrNode)
			{
				if ($attrName == 'index')
					$color_index = $attrNode->value;
				else if ($attrName == 'value')
					$color_value = $attrNode->value;

				if (is_null($color_index) || is_null($color_value))
					continue;

				if (isset(self::$dune_default_colors_values[$color_index]))
					$result &= ($color_value == self::$dune_default_colors_values[$color_index]);
			}
		}

		return (bool)$result;
	}


	private function color_palette_patch(&$error)
	{
		# Приводит неиспользуемый системой диапазон цветовой палитры текущего скина
		# в соответствие с базовым (дефолтным), и возвращает истину в случае успеха,
		# или фальш в противном случае ($error содержит текстовый комментарий
		# ошибки).
		# Внимание! Для применения патча системой (в случае успешного завершения
		# функции) требуется перезагрузить системную оболочку (shell).

		$error = '';
		clearstatcache();

		if (file_exists($this->skin_path . '/dune_skin_config.xml'))
		{
			$dom = new DomDocument();
			$dom->load($this->skin_path . '/dune_skin_config.xml');
			$color = $dom->getElementsByTagName('color');

			foreach ($color as $item)
			{
				$color_index = null;
				$color_value = null;
				foreach ($item->attributes as $attrName => $attrNode)
				{
					if ($attrName == 'index')
						$color_index = $attrNode->value;
					else if ($attrName == 'value')
						$color_value = $attrNode->value;

					if (is_null($color_index) || is_null($color_value))
						continue;

					if (isset(self::$dune_default_colors_values[$color_index]))
					{
						hd_print("Patched color value from " . $attrNode->value . " to " . self::$dune_default_colors_values[$color_index] ." for index $color_index");
						$attrNode->ownerElement->setAttribute('value', self::$dune_default_colors_values[$color_index]);
					}
				}
			}

			$xml = $dom->saveXML();
			$patched_skin_config = substr($xml, strpos($xml, '?'.'>') + 2);
			$dune_skin_config = file_get_contents($this->skin_path . '/dune_skin_config.xml');

			if (preg_match('/\/*firmware/', $this->skin_path))
			{
				$custom_skin_path = preg_replace('/(.*\/(flashdata|persistfs)).*$/', "$1", DuneSystem::$properties['data_dir_path']) . '/dune_skin';

				if (file_exists(get_paved_path($custom_skin_path)))
				{
        			foreach (glob("$custom_skin_path/*") as $file)
        			{
        				if (is_link($file))
							unlink($file);
						else if (is_dir($file))
						{
							shell_exec('rm -rf ' . $file . '/*');
							rmdir($file);
        				}
        				else
        					unlink($file);
        			}
   				}
   				else
   				{
   					$error = 'The directory for the custom skin in the system store is not available';
   					hd_print($error . '! The initiating process was terminated');

   					return false;
				}

				foreach (glob($this->skin_path . '/*') as $file)
				{
					$file = realpath($file);
					$basename = basename($file);

					if (is_dir($file))
						self::copy_all($file, "$custom_skin_path/$basename");
					else if ($basename == 'dune_skin_config.xml')
					{
						if (!file_put_contents("$custom_skin_path/$basename", $patched_skin_config))
						{
							$error = 'An unexpected error occurred when saving to save the "dune_skin_config.xml"';
							hd_print($error . '! The initiating process was terminated');

							return false;
						}
					}
					else if (!copy($file, "$custom_skin_path/$basename"))
					{
						$error = 'In the process of copying a skin file error occurred';
						hd_print($error . '! The initiating process was terminated');

						return false;
					}
				}

        		if ($system_settings = get_shell_settings())
        		{
					$system_settings['gui_skin'] = 'custom';
					self::$instance->reboot_action = ActionFactorySafe::change_settings($system_settings, false, true);
				}
			}
			else if (!file_put_contents($this->skin_path . '/dune_skin_config.xml', $patched_skin_config))
			{
				$error = 'An unexpected error occurred when saving to save the "dune_skin_config.xml"';
				hd_print($error . '! The initiating process was terminated');

				return false;
			}

			@file_put_contents(get_paved_path(DuneSystem::$properties['data_dir_path'] . '/backup') . '/' . md5($patched_skin_config), $dune_skin_config);
		}

		return empty($error);
	}

	///////////////////////////////////////////////////////////////////////////

	public function get_handler_id()
	{
		return 'dune_palette_patcher_handler';
	}

	///////////////////////////////////////////////////////////////////////////

	public static function init()
	{
		if (is_null(self::$instance))
			self::$instance = new DunePalettePatcher();

		clearstatcache();
		self::$checked = false;
		$fw = get_parsed_firmware_ver();
		self::$instance->reboot_action = ($fw['branch_num'] < 11)? null : ActionFactorySafe::stb_shell_reboot();
		self::$instance->skin_path = get_active_skin_path();
		$skin_properties = file_exists(self::$instance->skin_path . '/dune_skin.properties')? parse_ini_file(self::$instance->skin_path . '/dune_skin.properties') : array();
		self::$instance->skin_name = isset($skin_properties['name'])? $skin_properties['name'] : 'unknow';
		self::$instance->skin_version = isset($skin_properties['version'])? $skin_properties['version'] : 'n/a';
		hd_print('DunePalettePatcher initialized successfully!');
	}

	public static function get_skin_palette_check_action($post_action, $disable_action, $dialog_frame_style = DIALOG_FRAME_STYLE_DEFAULT)
	{
		if (is_null(self::$instance))
			throw new Exception(get_class(self::$instance).': The instance of the class was not initialized!');

		$checked = self::$checked;
		self::$checked = true;

		if (!$checked && !self::$instance->color_palette_check())
		{
			self::$instance->out_action = $post_action;
			ControlFactory::add_vgap($defs, 20);
			ControlFactory::add_multiline_label($defs, null, "Обнаружены некорректные данные в палитре цветов текущего скина, что может вызвать проблемы с отображением цветного текста в приложении. Это можно (и рекомендуется) исправить пропатчиванием палитры, результат не исказит баланса цветов в самом скине.\n\nПожалуйста, выберите действие:", 6);
			ControlFactory::add_vgap($defs, 40);
			ControlFactory::add_close_dialog_and_apply_button($button_defs, self::$instance, array('dialog_frame_style' => $dialog_frame_style), 'patch_palette', 'Пропатчить таблицу палитры', 900);
			ControlFactoryExt::add_button_centered($defs, $button_defs, 1350);
			ControlFactory::add_custom_close_dialog_and_apply_button($button_defs, null, 'Пропустить (оставить без изменений)', 900, $post_action);
			ControlFactoryExt::add_button_centered($defs, $button_defs, 1350);
			ControlFactory::add_custom_close_dialog_and_apply_button($button_defs, null, 'Пропустить и не проверять в будущем', 900, $disable_action);
			ControlFactoryExt::add_button_centered($defs, $button_defs, 1350);
			ControlFactory::add_vgap($defs, 20);

			return
				ActionFactory::show_dialog(
					'ПРЕДУПРЕЖДЕНИЕ!',
					$defs,
					false,
					1350,
					array
					(
						'dialog_params' => array('frame_style' => $dialog_frame_style)
					));
		}

		return null;
	}

	public static function get_uninstall_plugin_action($post_action = null, $dialog_frame_style = DIALOG_FRAME_STYLE_DEFAULT)
	{
		if (is_null(self::$instance))
			throw new Exception(get_class(self::$instance).': The instance of the class was not initialized!');

		self::$instance->out_action = $post_action;

		return
			UserInputHandlerRegistry::create_action(self::$instance, 'uninstall', array('dialog_frame_style' => $dialog_frame_style));
	}

	///////////////////////////////////////////////////////////////////////////

	public function handle_user_input(&$user_input, &$plugin_cookies)
	{
		static	$palette_backup_path;

		switch ($user_input->control_id)
		{
			case 'patch_palette':
				$err = '';

				if ($this->color_palette_patch($err))
				{
					$msg = "Операция выполнена успешно!\nДля применения внесенных изменений необходимо перезагрузить медиаплеер.";
					hd_print('Skin colors patched succesfull!');
				}
				else
				{
					$msg = "Операция прервана с сообщением:\n\"$err\"\n\nПожалуйста, перезагрузите медиаплеер и повторите попытку. Если проблема повторится, то сообщите о ней разработчику: brigadir@mydune.ru";
					hd_print('Skin colors patched failed! Please let me know about this problem at brigadir@mydune.ru');
				}

				ControlFactory::add_vgap($defs, 20);
				ControlFactory::add_multiline_label($defs, null, $msg, 12);
				ControlFactory::add_vgap($defs, 40);
				ControlFactory::add_custom_close_dialog_and_apply_button($button_defs, '', empty($err)? 'Перезагрузить' : 'Продолжить', 400, empty($err)? $this->reboot_action : $this->out_action);
				ControlFactoryExt::add_button_centered($defs, $button_defs, 1400);
				ControlFactory::add_vgap($defs, 20);

				return
					ActionFactory::show_dialog('Патч палитры Dune', $defs, false, 1400, array('dialog_params' => array('frame_style' => $user_input->dialog_frame_style)));

			case 'restore_palette':
				if (file_exists($palette_backup_path))
				{
					$custom_skin_path = file_exists('/persistfs')? '/persistfs/dune_skin/' : '/flashdata/dune_skin/';

					if (file_exists($custom_skin_path . 'dune_skin_config.xml'))
					{
						rename($palette_backup_path, $custom_skin_path . 'dune_skin_config.xml');
						$palette_backup_path = '';
					}
				}

				if (empty($palette_backup_path))
				{
					$msg = "Палитра цветов в текущем скине успешно восстановлена!\n\nДля применения системой внесенных изменений не забудьте перезагрузить медиаплеер.\n\nУдалить плагин?";
					hd_print('Skin colors restored succesfull!');
				}
				else
					$msg = "";

				ControlFactory::add_vgap($defs, 20);
				ControlFactory::add_multiline_label($defs, null, $msg, 10);
				ControlFactory::add_vgap($defs, 40);
				ControlFactory::add_custom_close_dialog_and_apply_button($button_defs, '', 'Удалить', 400, self::$instance->out_action);
				ControlFactoryExt::add_button_centered($defs, $button_defs, 1400);
				ControlFactory::add_custom_close_dialog_and_apply_button($button_defs, '', 'Отмена', 400, $this->reboot_action);
				ControlFactoryExt::add_button_centered($defs, $button_defs, 1400);
				ControlFactory::add_vgap($defs, 20);

				return ActionFactory::show_dialog(
					'Восстановление палитры Dune',
					$defs,
					false,
					1400,
					array
					(
						'dialog_params' => array('frame_style' => $user_input->dialog_frame_style),
					));

			case 'uninstall':
				$custom_skin_path = file_exists('/persistfs')? '/persistfs/dune_skin/' : '/flashdata/dune_skin/';
				$backup_storage_path = DuneSystem::$properties['data_dir_path'] . '/backup';

				if (file_exists($custom_skin_path . 'dune_skin_config.xml') && file_exists($backup_storage_path))
				{
					$hash = md5(file_get_contents($custom_skin_path . 'dune_skin_config.xml'));

        			foreach (glob($backup_storage_path . '/*') as $file)
        			{
        				if (basename($file) == $hash)
        				{
        					$palette_backup_path = $file;
        					$add_params = array('dialog_frame_style' => $user_input->dialog_frame_style);
							ControlFactory::add_vgap($defs, 20);
							ControlFactory::add_multiline_label($defs, null, "Таблица палитры цветов в текущем скине модифицирована. После удаления плагина восстановить палитру к первоначальному состоянию будет возможно только путем повторной установки скина. Также возможно восстановить палитру цветов прямо сейчас, после чего плагин будет удален.\n\nПожалуйста, выберите действие:", 10);
							ControlFactory::add_vgap($defs, 40);
							ControlFactory::add_close_dialog_and_apply_button($button_defs, self::$instance, $add_params, 'restore_palette', 'Восстановить таблицу палитры', 900);
							ControlFactoryExt::add_button_centered($defs, $button_defs, 1400);
							ControlFactory::add_close_dialog_and_apply_button($button_defs, self::$instance, $add_params, 'continue_uninstall', 'Не восстанавливать и продолжить', 900);
							ControlFactoryExt::add_button_centered($defs, $button_defs, 1400);
							ControlFactory::add_vgap($defs, 20);

							return
								ActionFactory::show_dialog(
									'ПРЕДУПРЕЖДЕНИЕ!',
									$defs,
									false,
									1400,
									array
									(
										'dialog_params' => array('frame_style' => $user_input->dialog_frame_style), 'initial_sel_ndx' => 1
									));
						}
        			}
				}

			case 'continue_uninstall':
				ControlFactoryExt::add_vgap($defs, 10);
				ControlFactoryExt::add_label($defs, null, 'Приложение будет удалено. Вы уверены?');
				ControlFactoryExt::add_vgap($defs, 40);
				ControlFactoryExt::add_custom_close_dialog_and_apply_button($button_defs, null, 'Удалить', 400, self::$instance->out_action);
				ControlFactoryExt::add_button_centered($defs, $button_defs, 1050);
				ControlFactoryExt::add_custom_close_dialog_and_apply_button($button_defs, null, 'Отменить', 400, $this->reboot_action);
				ControlFactoryExt::add_button_centered($defs, $button_defs, 1050);
				ControlFactoryExt::add_vgap($defs, 10);
				return
					SplashScreen::open_and_run(ActionFactorySafe::show_dialog(null, $defs, false, 1050));

			case 'hard_reset':
				hd_print('Do reboot stb...');
				return
					ActionFactorySafe::stb_reboot();
		}

		return null;
	}
}
?>
