<?php
/*
	Clase Traducciones
	@version 1.0
*/

//26/Septiembre/2019 ==> Añadimos la carga como objeto de WebStore
	//La funcionalidad de las traducciones copiada de webStore (expandir de objeto, campos, where, etc.) es para utilizar las traudcciones en el admin
	//Sin embargo en la web no se cargan usando dicha funcionalidad, se cargan, o sobre la marcha, o todas al final par ahorrar tiempo
class SuperTraduccion extends Objeto{
	//Campos que debe llevar la clase de por sí
	public static $campos_tabla = array();
	public static $alias = '';
	public static $nombreTabla = '';
	public static $camposSelect = '';
	public static $whereSelect = '';
	public static $publicFieldList = array();
	
	//Código del idioma maestro:
	private static $MAE = 'MAE';
	
	//campos de la tabla y otros:
	public $codigo = 0;
	public $idioma = 'ES';
	public $objetoIdioma = NULL;
	public $frase = '';
	public $traducciones = array();
	
	private static $phraseList = array();
	private static $timesCalled = 0;	//Con este contador voy a tener en cuenta cuantas veces hemos traducido una frase
	private static $realCalled = 0;		//Con este contador voy a tener en cuenta cuantas veces ha sido necesario consultar de la BD (A veces hay frases repetidas)
	private static $onTheFly = false;	//Esta variable indicará si las traducciones se realiarán "on the fly" o al final. Es útil para los emails
	
	
	public static function getCampos($soloPrincipal = NULL){
		static::$nombreTabla = strtolower(get_called_class());
		static::getTablaRelacionada();
		//static::$campos_tabla = BaseDatos::$dictionary->{static::$nombreTabla . 'Campos'}; //==> Comento esta línea porque creo que no sirve pa na
		//Una vez que tenemos el nombre de la tabla, vamos a obtener los campos de la misma y los nombres para la selec
		if (static::$nombreTabla != ''){
			$campos = ' ';
			if ($soloPrincipal || $soloPrincipal === NULL){
				//$campos .= BaseDatos::dameCamposAlias(BaseDatos::$dictionary->{static::$nombreTabla . 'Campos'}, static::getAlias());
				$campos .= BaseDatos::dameCamposAlias(static::$nombreTabla);
			}
			if ($soloPrincipal === false || $soloPrincipal === NULL){
				$campos .= ( (trim($campos) != ' ') ? ', ' : '' ) . 
				Idioma::getCampos();
			}
			if ($soloPrincipal == NULL){
				static::$camposSelect = $campos;
			}
		}
		return($campos);
	}
	
	public static function getNombreTabla($soloPrincipal = NULL){
		//Incluimos los globals para coger el valor del idioma:
		static::$nombreTabla = strtolower(get_called_class());
		static::getTablaRelacionada();
		//Ahora vamos a componer el nombre de la tabla teniendo en cuenta si es sólo principal, si no o si es todo (NULL)
		$consulta = ' ';
		if ($soloPrincipal || $soloPrincipal === NULL){
			$consulta .= static::$nombreTabla . ' as `' .  static::getAlias() . '` ';
		}
		
		if ($soloPrincipal === false || $soloPrincipal === NULL){
			$consulta .= static::getJoin('Traduccion', 'idioma', 'Idioma', 'codigo');
		}
		$consulta .= ' ';
		
		//Sólo asignamos el nombre de la tabla si no nos piden más parámetros
		if ($soloPrincipal == NULL){
			static::$nombreTabla = $consulta;
		}
		return($consulta);
	}

	//Funciones de carga en la BD, van aquí arriba
	public static function getWhereTabla(){
		$argumentos = func_get_args();
		$id = isset($argumentos[0]) ? $argumentos[0] : NULL;
		$idioma = isset($argumentos[1]) ? $argumentos[1] : '';	//Si en vez de NULL, nos pasan el idioma como cadena vacía es porque no se quiere aplicar este filtrado
		$frase = isset($argumentos[2]) ? $argumentos[2] : NULL;
		$limitar = isset($argumentos[3]) ? $argumentos[3] : true;

		//include('comunes/globals.php');
		static::$whereSelect = '';
		if ($id !== NULL) {
			static::$whereSelect .= ' `' . static::getAlias() . '`.`codigo` = "' . $id . '" ';
		}
		if ($idioma !== '') {
			static::$whereSelect .= ( (static::$whereSelect != '') ? ' AND ' : '') . ' `' . static::getAlias() . '`.`idioma` = "' . $idioma . '" ';
		}
		if ($frase !== NULL) {
			static::$whereSelect .= ( (static::$whereSelect != '') ? ' AND ' : '') . ' `' . static::getAlias() . '`.`frase` = "' . $frase . '" ';
		}
		if ($limitar){
			static::$whereSelect .= ' LIMIT 1 ';
		}
		return(static::$whereSelect);
	}
	
	
    function __construct($params){
		//Vamos a decodificar los parámetros:
		//Por defecto nos van a mandar la cadena de texto:
		$frase = '';
		if (is_string($params)){
			$frase = $params;
		}
		$params = new ObOptions($params);

		//La fila es imprescindible para la carga dinámica de objetos en webStore
		$filaTraduccion = $params->getDefaultValue('filaTraduccion', NULL);
		//Aquí pongo el resto de de parámetros que podemos necesitar en la carga
		$ID = $params->getDefaultValue('ID', 0);
		$idioma = $params->getDefaultValue('idioma', self::$MAE);	//El idioma siempre es por defecto el "maestro"
		$frase = $params->getDefaultValue('frase', NULL);
		$cargarTraducciones = $params->getDefaultValue('cargarTraducciones', true);	//Indica si vamos a cargar las traducciones para este idioma
		//Aquí dejo comentada una función que me va a dar el idioma por defecto de la web
		//Sesion::get('idioma', Shop::$configuracion->idioma);
		$fila = NULL;
		
		
		if ($filaTraduccion == NULL){
			if ($ID > 0) {
				//Preparamos el FROM con las uniones correspondientes
				$sentencia_sql = 'SELECT ' . static::getCampos() . ' 
									FROM ' . static::getNombreTabla() . '
									WHERE ' . static::getWhereTabla($ID, $idioma, $frase);
				//echo("$sentencia_sql<br/>\n");
				$bd = new BaseDatos();
				if ($bd->isConectado()){
					$bd->setConsultaSQL($sentencia_sql);
					$fila = $bd->getFila();
				}
			}
		}else{
			$fila = $filaTraduccion;
		}
		if($fila != null){
			$this->extractData($fila);
			if ($cargarTraducciones){
				$this->cargar_traducciones();
			}
		}
    }
	
	private function extractData($fila){
		$this->codigo = $this->getProp($fila, 'codigo');
		$this->idioma = $this->getProp($fila, 'idioma');
		$this->frase = $this->getProp($fila, 'frase');
		//Vamos a cargar el objeto idioma:
		$this->objetoIdioma = new Idioma(array('filaIdioma' => $fila, 'ID' => $this->idioma));
		//Aquí vamos a controlar que si no hay objeto idioma, cojamos el del idioma:
		if ( ($this->idioma == '') && ($this->objetoIdioma != NULL) && ($this->objetoIdioma->codigo != '') ){
			$this->idioma = $this->objetoIdioma->codigo;
		}
	}
	
	private function cargar_traducciones(){
		$sentencia_sql = 'SELECT ' . static::getCampos() . ' 
							FROM ' . static::getNombreTabla(true) . //Vamos a hacer el primer right join de webStore
							static::getJoin('Traduccion', array('idioma', 'codigo'), 'Idioma', array('codigo', '$' . $this->codigo) , NULL, NULL, ' RIGHT JOIN ') . ' 
							WHERE ' . Idioma::getWhereTabla(NULL, true, false);
		//echo("<pre>{$sentencia_sql}</pre>");
		$bd = new BaseDatos();
		if ($bd->isConectado()){
			$bd->setConsultaSQL($sentencia_sql);
			while ($fila = $bd->getFila()){
				$idioma = $this->getProp($fila, 'idioma');
				if ($idioma != self::$MAE){
					$this->traducciones[] = new Traduccion(array('filaTraduccion' => $fila, 'cargarTraducciones' => false));
				}
			}
		}
	}
	
    function __destruct(){
        
    }
	
	
	public static function addPhrase($phrase, $languaje = NULL){
		//Antes de nada comprobamos que todo sea correcto:
		$languaje = ($languaje == NULL) ? Sesion::get('idioma', Shop::$configuracion->idioma) : $languaje;
		self::$timesCalled++;	//Incrementamos el número de veces que se ha llamado a la función de traducir
		if (trim($phrase) != ''){
			//Nos hemos asegurado de que exista un array para el idioma en cuestión, vamos a ver si la frase existe o no:
			$subArray = array($phrase, $languaje);
			if (!in_array($subArray, self::$phraseList, true)){
				self::$phraseList[] = $subArray;
				self::$realCalled++;	//Incrementamos el contador que registra las veces que "Sí" se ha traducido una palabra
			}
		}
	}

	//Funciones que vamos a utilizar para la traducción:
	
	//La función de traducir añadirá la frase al array o la traducirá directamente, según como esté configurado el programa
	public static function traducir($frase, $idioma = NULL){
		//Antes de nada "sanitizamos" la frase quitando las comillas y las comillas dobles
		$frase = str_replace(array('\'', '"'), array('&apos;', '&quot;'), $frase);
		if ($idioma == NULL){
			$idioma = Sesion::get('idioma', Shop::$configuracion->idioma);
		}
		//TODO VARIABLE OPTIMIZATION	==> En el futuro podremos elegir, según configuración si hacer la traducción aquí o agruparlas todas para hacer las al final
		$retorno = $frase;
		//Si tenemos activadas las traducciones, hacemos esto:
		if (Module::is_active('multi-idioma')){
			static::addPhrase($frase, $idioma);
			if (self::$onTheFly){
				$retorno = static::traducirPhrase($frase, $idioma);
			}else{
				//$retorno = self::traducirVariables($retorno);
				$retorno = 'traducir(\'' . $retorno . '\');';	//Añado las "\" para escapar caracteres especiales que deban de ser transportados
				//FileSystem::log($retorno);
			}
		}else{
			//Si no tenemos activadas las traducciones, vamos a traducir sólo las variables
			if (self::$onTheFly){
				$retorno = self::traducirVariables($retorno);
			}else{
				$retorno = htmlentities(self::traducirVariables($retorno), ENT_QUOTES | ENT_HTML5, ini_get("default_charset"), false);
			}
		}
		return($retorno);
	}

	//Esta función será la encargada de traducir el array completo de texto y añadirlo a a la web
	public static function traducirPhrase($frase, $idioma = NULL){
		$retorno = $frase;
		//Hacemos la consulta a la BD
		$bd = new BaseDatos();
		if ( ($bd->isConectado()) && ($idioma != NULL) ){
			$sentencia_sql = static::generateMySqlSelec($frase, $idioma);
			//echo("<!-- $sentencia_sql -->");
			$bd->setConsultaSQL($sentencia_sql);
			if ($fila = $bd->getFila()){
				//Si hay frase, vamos a por ella:
				$retorno = $fila['frase'];
			}else{
				$retorno = $frase;
				//No hay fila, hay que insertar
				$sentencia_sql = self::generateMySqlInsert($retorno);
				$bd->setConsultaSQL($sentencia_sql);
			}
		}
		return(self::traducirVariables($retorno));
		return($retorno);
	}
	
	private static function generateMySqlSelec($frase, $idioma){
		//en la BD hay que escapara la cadena porque si no se guarda interpretada
		$sentencia_sql = 'SELECT IFNULL(`b`.`frase`, `a`.`frase`) as `frase` FROM `traduccion` `a` LEFT JOIN `traduccion` `b` 
				on ( `a`.`codigo` = `b`.`codigo` AND `b`.`idioma` = \'' . $idioma . '\') 
				WHERE (`a`.`frase` = \'' . addslashes($frase) . '\' AND `a`.idioma = \'' . self::$MAE . '\' )
				LIMIT 1';
		return($sentencia_sql);
	}
	
	private static function generateMySqlInsert($frase){
		$sentencia_sql = 'INSERT INTO `traduccion` (`idioma`, `frase`) VALUES (\'' . self::$MAE . '\', \'' . addslashes($frase) . '\');';
		return($sentencia_sql);
	}
	
	//Esta función traduce las variables que haya en una cadena a su valor (variables estáticas de clases)
	private static function traducirVariables($frase){
		$retorno = $frase;
		//Modificado por Pedro el 24/Septiembre/2019 ==> vamos a tener los caracteres %- y -% como separadores de "literales"
		$inicioVariable = '%-';
		$finVariable = '-%';
		$listaVariables = array();
		$total_variables = substr_count($retorno, $inicioVariable);
		$posicion = 0;
		$siguiente = 0;
		while( (($siguiente) < strlen($retorno)) && ($posicion = strpos($retorno, $inicioVariable, $siguiente)) !== false){
			$siguiente = strpos($retorno, $finVariable, $posicion);
			//Si siguiente es falso, entonces lo ponemos al final de la cadena:
			$siguiente = ($siguiente === false) ? strlen($retorno) : $siguiente;
			//Vamos a extraer la cadena:
			$start = $posicion + strlen($inicioVariable);
			$length = $siguiente - $start;
			$listaVariables[] = '%-' . substr($retorno, $start, $length) . '-%';
		}

		//Modificado por Pedro el 24/Septiembre/2019 ==> Vamos a obtener el resultado de ejecutar la variable, pero a nuestra manera
		$listaResultados = array();
		foreach($listaVariables as $key => $variable){
			$variable = trim($variable);
			//Identificamos la variable:
			$variable = str_replace(array('%-','-%'), '', $variable);
			$listaResultados[] = static::WsEval($variable);
		}
		//Sólo falta hacer el replace:
		$retorno = str_replace($listaVariables, $listaResultados, $retorno);
		
		return($retorno);
		//return($retorno);
		//return(htmlentities($retorno, ENT_HTML5, "ISO-8859-1"));
	}
	
	
	public static function WsEval($string, $objetoPadre = NULL){
		$return = '';
		//Si no tenemos objeto padre, tenemos que encontrarlo:
		if ($objetoPadre == NULL){
			//En esta primera parte vamos a extraer la clase y la primera variable a la que hacemos referencia
				//Esta variable ha de ser, por supuesto, estática para que sea visible desde todos sitios
			//Buscamo la clase:
			if ( ($posClass = strpos($string, '::$')) !== false){
				$clase = substr($string, 0, $posClass);
				$subStr = substr($string, $posClass + 3 );
				$posFlecha = strpos($subStr, '->');
				$posFlecha = ($posFlecha === false) ? strlen($subStr) : $posFlecha;
				//Ahora vamos a obtener el objeto que está en esta clase:
				$objeto = substr($subStr, 0, $posFlecha );
				//Vamos a obtener el objeto (que puede ser una variable:
				$objetoPadre = $clase::${$objeto};

				//En la cadena aún no hemos quitado el objeto, si hay subvariables hay que quitarlas, si no, ya tenemos la string:
				if ($posFlecha != strlen($subStr)){
					$subStr = substr($subStr, $posFlecha + 2 );
				}
				
				$string = $subStr;
			}
		}
		
		//La idea es que el objetoPadre contenga la variable estática si es la primera entrada
			//o contenga un objeto con subvariables
		//Llegados a este punto, si tenemos objetoPadre vamos ver si ejecutar de nuevo la función:
		if ($objetoPadre != NULL){
			//Si el objeto padre no es un objeto lo devolvemos directamente
			if (!is_object($objetoPadre)){
				$return = $objetoPadre;
			}else{
				//El objeto padre es un objeto y tengo una cadena de texto que hace referencia a algún hijo, vamos a llegar hasta ese hijos
				if ($string != ''){
					//Comprobamos que sólo tengamos un hijo o tengamos más bucando flechas
					$posFlecha = strpos($string, '->');
					$posFlecha = ($posFlecha === false) ? strlen($string) : $posFlecha;
					$substr = substr($string, 0, $posFlecha);
					$objetoPadre = $objetoPadre->$substr;
					//Aquí tenemos el hijo cargado en el objeto Padre, miramos si hay más hijos, si no lo s hay devolvemos ya el padre
					if ($posFlecha == strlen($string)){
						$return = $objetoPadre;
					}else{
						//Si hay más hijos, eliminamos el primer elemento de la lista de hijos y llamamos de nuevo a la función
						$string = substr($string, $posFlecha + 2);
						$return = static::WsEval($string, $objetoPadre);
					}
				}
			}

			if (is_object($objetoPadre)){
				if ($string != ''){
					
				}
			}else{
				$return = $objetoPadre;
			}
		}
		
		//Siempre devolvemos aquí una cadena vacía por seguridad:
		return($return);
	}
	
	public static function aplyPhraseList($string){
		
		//Vamos a generar una select con el array:
		$mega_select = '';
		$finalArray = array();
		//ob_start();
		foreach(self::$phraseList as $key => $element){
			$phrase = $element[0];
			$languaje = $element[1];
			$mega_select .= ( ($mega_select != '') ? ' UNION ' : '') . '( SELECT \'' . $key . '\' `counter`, IFNULL( (' . self::generateMySqlSelec($phrase, $languaje) . '), \'NULL\') `frase` )
			';
		}
		//traza:
		//$string = $mega_select;
		///echo($mega_select);
		//$string = ob_get_clean();
		//FileSystem::log($mega_select);
		//Si tenemos select la ejecutamos y recorremos sus resultados
		if ($mega_select != ''){
			//Vamos a guardar una insert dónde insertaremos todos los elementos que no hay:
			$insert_select = '';
			//Vamos a hacer la select:
			$bd = new BaseDatos();
			$traduccionFinal = array();
			$idioma = Sesion::get('idioma', Shop::$configuracion->idioma);
			if ( ($bd->isConectado()) && ($idioma != NULL) ){
				$contador = 0;
				$bd->setConsultaSQL($mega_select);
				while ($fila = $bd->getFila()){
					//Cogemos la frase resultado:
					$frase = $fila['frase'];
					//$string .= '<p>' . $frase . '</p>';
					//Si la fila es la cadena 'NULL', entonces hay que hacer una insert:
					$frase_a_buscar = self::$phraseList[$contador][0];
					if ($frase == 'NULL'){
						//Corregimos la frase y la anotamos para insertar
						$frase = $frase_a_buscar;
						$insert_select .= self::generateMySqlInsert($frase);
					}
					//Añadimos la frase a la traduccion final:
					//$traduccionFinal[]= htmlentities(self::traducirVariables($frase));
					$traduccionFinal[]= self::traducirVariables($frase);
					//Además tenemos que dejar en el array de origen la frase con el "traducir". OJO aquí hay que poner la frase original, es muy importante, porque si no pondremos una frase que habrá pasado por mysql
					$frase = 'traducir(\'' . $frase_a_buscar . '\');';
					$finalArray[] = $frase;
					$contador++;
				}
			}
			//traza
			/*foreach($traduccionFinal as $key => $value){
				FileSystem::log($value);
			}
			foreach($finalArray as $key => $value){
				FileSystem::log($value);
			}*/
			if ($insert_select != ''){
				//echo("<p>$insert_select</p>");
				//Hacemos una insert:
				$bd->setConsultaSQL($insert_select);
			}
			
			//Por último vamos a reemplazar las cadenas:
			//Pero ojo, que $string puede venir de una función remota, por lo tanto está en formato json y hay que tener cuidado:
			$jsonObect = json_decode($string);
			if ($jsonObect === NULL){
				$string = str_replace($finalArray, $traduccionFinal, $string);
			}else{
				//En este caso tenemos que recorrer el objeto json:
				if ( (isset($jsonObect->data)) && (count($jsonObect->data) > 0) ){
					foreach($jsonObect->data as $clave => $objeto){
						//Traducimos cada elemento y lo volvemos a insertar
						if (isset($objeto->content)){
							//FileSystem::log($objeto->content);
							$objeto->content = str_replace($finalArray, $traduccionFinal, $objeto->content);
							$jsonObect->data[$clave] = $objeto;
						}
					}
				}
				//Volvemos a convertir el objeto en json
				$string = json_encode($jsonObect);
				//FileSystem::log($string);
			}

			
			//traza:
			/*$string2 = '';
			$string2 = 'Operación de traducción: ' . PHP_EOL . 'Buscamos ' . PHP_EOL;
			foreach($finalArray as $key => $value){
				$string2 .= $value . PHP_EOL;
			}
			$string2 .= 'Reemplazamos ' . PHP_EOL;
			foreach($traduccionFinal as $key => $value){
				$string2 .= $value . PHP_EOL;
			}
			//$string2 .= $mega_select;
			$string2 .= 'En la frase: ' . PHP_EOL . $string . PHP_EOL;
			FileSystem::log($string2);*/
		}
		return($string);
	}
	
	public static function getMae(){
		return(self::$MAE);
	}
	public static function getPhraseList(){
		return(self::$phraseList);
	}
	public static function getTimesCalled(){
		return(self::$timesCalled);
	}
	public static function getRealCalled(){
		return(self::$realCalled);
	}
	
	
	//-----------------------------------------------
	//	FUNCIONES REMOTAS	
	//-----------------------------------------------
	public static function remoteInsertOrUpdate($params){
		//Vamos a capturar la salida:
		$retorno = new stdClass();
		//Vamos a extraer de "data" los datos a los que vamos a enviar:
		$data = $params;
		if (is_string($data)){
			$data = json_decode($params);
			if ($data == NULL){
				$data = $params;
			}
		}
		$idioma = (isset($data->idioma)) ? $data->idioma : 0;
		$codigo = (isset($data->codigo)) ? $data->codigo : 0;
		$frase = (isset($data->frase)) ? $data->frase : 0;
		;
		static::insertOrUpdate(array('codigo', 'idioma', 'frase'),array($codigo, $idioma, addslashes($frase)));
		$traduccion = new Traduccion(array('ID' => $codigo, 'idioma' => $idioma));
		$retorno->resultado = $traduccion;

		//Capturamos la salida y la metemos en el msgs
		$retorno = json_encode($retorno);
		return($retorno);
	}
	
	public static function onTheFlyOn(){
		self::$onTheFly = true;
	}
	public static function onTheFlyOff(){
		self::$onTheFly = false;
	}
}
?>