!C99Shell v. 2.1 [PHP 8 Update] [02.02.2022]!

Software: Apache/2.4.53 (Unix) OpenSSL/1.1.1o PHP/7.4.29 mod_perl/2.0.12 Perl/v5.34.1. PHP/7.4.29 

uname -a: Linux vps-2738122-x 4.15.0-213-generic #224-Ubuntu SMP Mon Jun 19 13:30:12 UTC 2023 x86_64 

uid=1(daemon) gid=1(daemon) grupos=1(daemon) 

Safe-mode: OFF (not secure)

/opt/apex_tdfonline/php/nucleo/componentes/persistencia/   drwxr-xr-x
Free 13.81 GB of 61.93 GB (22.3%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Feedback    Self remove    Logout    


Viewing file:     toba_datos_tabla.php (61.79 KB)      -rw-r--r--
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
/**
 * Representa una estructura tabular tipo tabla o RecordSet en memoria
 *
 * - Utiliza un administrador de persistencia para obtener y sincronizar los datos con un medio de persistencia.
 * - Una vez en memoria existen primitivas para trabajar sobre estos datos.
 * - Los datos y sus modificaciones son mantenidos automáticamente en sesión entre los distintos pedidos de página.
 * - Una vez terminada la edición se hace la sincronización con el medio de persistencia marcando el final de la transacción de negocios.
 *
 * @package Componentes
 * @subpackage Persistencia
 * @todo Control de FK y PK
 */
class toba_datos_tabla extends toba_componente 
{
    protected 
$_info_estructura;
    protected 
$_info_columnas;
    protected 
$_info_externas;
    protected 
$_info_externas_col;
    protected 
$_persistidor;                        // Mantiene el persistidor del OBJETO
    // Definicion asociada a la TABLA
    
protected $_clave = array();                            // Columnas que constituyen la clave de la tabla
    
protected $_columnas;
    protected 
$_posee_columnas_ext false;        // Indica si la tabla posee columnas externas (cargadas a travez de un mecanismo especial)
    //Constraints
    
protected $_no_duplicado;                    // Combinaciones de columnas que no pueden duplicarse
    // Definicion general
    
protected $_tope_max_filas;                    // Cantidad de maxima de datos permitida.
    
protected $_tope_min_filas;                    // Cantidad de minima de datos permitida.
    
protected $_es_unico_registro=true;        //La tabla tiene com maximo un registro?
    // ESTADO
    
protected $_cambios = array();                // Cambios realizados sobre los datos
    
protected $_datos = array();                    // Datos cargados en el db_filas
    
protected $_proxima_fila 0;                // Posicion del proximo registro en el array de datos
    
protected $_cursor;                            // Puntero a una fila específica
    
protected $_cursor_original;                    // Backup del cursor que se usa para deshacer un seteo
    
protected $_cargada false;
    protected 
$_from;
    protected 
$_where;
    
//Valores de las columnas BLOB
    
protected $_blobs = array();                    //Arreglo [$fila][$columna]['fp' => fp, 'path' =>string, 'modificado' => boolean]
                                                    // Si [$fila][$columna] 
                                                            //es null quiere decir que no esta cargado en la transaccion
                                                            //es array(...,'modificado' => true) hay que actualizarlo a la base
                                                            //es array(...,'modificado' => false) se cargo en la transacion pero no se modifico
    // Relaciones con el exterior
    
protected $_relaciones_con_padres = array();            // ARRAY con un objeto RELACION por cada PADRE de la tabla
    
protected $_relaciones_con_hijos = array();            // ARRAY con un objeto RELACION por cada HIJO de la tabla

    /**
     * @ignore 
     */
    
final function __construct($id)
    {
        
$propiedades = array();
        
$propiedades[] = "_cambios";
        
$propiedades[] = "_datos";
        
$propiedades[] = "_proxima_fila";
        
$propiedades[] = "_cursor";
        
$propiedades[] = "_cargada";
        
$propiedades[] = "_blobs";
        
$this->set_propiedades_sesion($propiedades);        
        
parent::__construct($id);
        for(
$a=0$a count($this->_info_columnas); $a++){
            
//Armo una propiedad "columnas" para acceder a la definicion mas facil
            
$this->_columnas$this->_info_columnas[$a]['columna'] ] =& $this->_info_columnas[$a];
            if(
$this->_info_columnas[$a]['pk']==1){
                
$this->_clave[] = $this->_info_columnas[$a]['columna'];
            }
            if(
$this->_info_columnas[$a]['externa']==1){
                
$this->_posee_columnas_ext true;
            }
        }
        
$this->activar_cargas_externas();
        
$this->activar_control_valores_unicos();
    }

    
    
/**
     * Ventana para agregar configuraciones particulares antes de que el objeto sea construido en su totalidad
     * @ventana
     */
    
function ini(){}

    
    
/**
     * Destructor del componente
     */    
    
function destruir()
    {
        
//-- Recorre los blobs modificados que tienen un resource y los convierte a nombre de archivos
        
foreach(array_keys($this->_blobs) as $id_fila) {
            foreach(
array_keys($this->_blobs[$id_fila]) as $id_campo) {
                
//-- Hay blob?
                
if (isset($this->_blobs[$id_fila][$id_campo])) {
                     
//-- Si no se subio a un archivo
                    
if ($this->_blobs[$id_fila][$id_campo]['path'] == '') {
                        
$fp $this->_blobs[$id_fila][$id_campo]['fp'];                    
                        if (
is_resource($fp)) {
                            
//-- Lo convierte a archivo
                            
rewind($fp);
                              
$temp_nombre toba::proyecto()->get_path_temp().'/'.md5(uniqid(time()));
                              
$fpd fopen($temp_nombre'w');
                              
stream_copy_to_stream($fp$fpd);
                              
fclose($fpd);
                            
//-- Guarda el path
                            
$this->_blobs[$id_fila][$id_campo]['path'] = $temp_nombre;
                        }
                    }
                    
//--Borra la referencia al fp, ya esta subido al Sist.Archv
                    
$this->_blobs[$id_fila][$id_campo]['fp'] = null;
                } else {
                    unset(
$this->_blobs[$id_fila][$id_campo]);
                }
            }
        }
        
parent::destruir();
    }

    
/**
     * @ignore 
     */
    
protected function activar_cargas_externas()
    {
        
//--- Se recorren las cargas externas, el lugar ideal seria hacer esto en el ap, pero aca es mas simple y eficiente
        
if ($this->_posee_columnas_ext) {
            foreach(
$this->_info_externas as $externa) {
                
$parametros = array();
                
$resultados = array();
                
//-- Se identifican las columnas de esta carga
                
foreach($this->_info_externas_col as $ext_col) {
                    if (
$ext_col['externa_id'] == $externa['externa_id']) {
                        if (
$ext_col['es_resultado'] == 1) {
                            
$resultados[] = $ext_col['columna'];
                        } else {
                            
$parametros[] = $ext_col['columna'];
                        }    
                    }                    
                }
                if (
$externa['sql'] != '') {
                    
//---Caso SQL
                    
$this->persistidor()->activar_proceso_carga_externa_sql(
                            
$externa['sql'], $parametros$resultados$externa['sincro_continua'], $externa['dato_estricto']);
                } else {
                    
//---- Caso de carga mediante DAO se divide en 3 casos
                    
$this->definir_metodo_carga_dao($externa$resultados$parametros);
                }
            }
        }        
    }
    
    protected function 
definir_metodo_carga_dao($externa$resultados$parametros)
    {
        
//-- La carga se realiza por una clase consulta php
        
if (isset($externa['carga_consulta_php']) && !is_null($externa['carga_consulta_php'])) {
            
$this->persistidor()->activar_proceso_carga_externa_consulta_php(
                                
$externa['metodo'], $externa['carga_consulta_php'], $parametros$resultados$externa['sincro_continua'],
                                
$externa['dato_estricto'], $externa['permite_carga_masiva'], $externa['metodo_masivo']);

        }elseif (isset(
$externa['carga_dt']) && ($externa['carga_dt'] != '')) {    //--Se carga mediante datos_tabla
            
$this->persistidor()->activar_proceso_carga_externa_datos_tabla(
                                
$externa['carga_dt'], $externa['metodo'],$parametros,
                                
$resultados$externa['sincro_continua'], $externa['dato_estricto'], $externa['permite_carga_masiva'], $externa['metodo_masivo']);
        } else {        
//Se carga con llamada estatica a una clase especifica.
            
$this->persistidor()->activar_proceso_carga_externa_dao(
                                
$externa['metodo'], $externa['clase'], $externa['include'],
                                
$parametros$resultados$externa['sincro_continua'], $externa['dato_estricto'], $externa['permite_carga_masiva'], $externa['metodo_masivo']);
        }        
    }

    
/**
     * @ignore 
     */
    
protected function activar_control_valores_unicos()
    {
        foreach( 
$this->_info_valores_unicos as $regla ) {
            if(isset(
$regla['columnas'])) {
                
$columnas explode(',',$regla['columnas']);
                
$columnas array_map('trim'$columnas);
                
$this->set_no_duplicado$columnas );
            }
        }
    }
    
    function 
set_definicion_columna($columna$propiedad$valor
    {
        
$this->_columnas[$columna][$propiedad] = $valor;
    }
    
    
/**
     * Reserva un id interno y lo retorna
     */
    
function reservar_id_fila()
    {
        
$actual $this->_proxima_fila;
        
$this->_proxima_fila++;
        return 
$actual;
    }

    
/**
     * Retorna el proximo id interno a ser utilizado
     */
    
function get_proximo_id()
    {
        return 
$this->_proxima_fila;    
    }

    
/**
     * Shorcut a toba::logger()->debug incluyendo infomación básica del componente
     */
    
protected function log($txt)
    {
        
toba::logger()->debug("TABLA: [{$this->get_tabla()}]\n".$txt'toba');
    }
    
//-------------------------------------------------------------------------------
    //--  Relacion con otros ELEMENTOS
    //-------------------------------------------------------------------------------

    /**
     * Informa a la tabla que existe una tabla padre
     * @param toba_relacion_entre_tablas $relacion
     * @ignore 
     */
    
function agregar_relacion_con_padre($relacion$id_padre)
    {
        
$this->_relaciones_con_padres[$id_padre] = $relacion;
    }
    
    
/**
     * Retorna las relaciones con las tablas padre
     * @return array de {@link toba_relacion_entre_tablas toba_relacion_entre_tablas}
     * @ignore 
     */
    
function get_relaciones_con_padres()
    {
        return 
$this->_relaciones_con_padres;
    }

    
/**
     * Retorna la relación con una tabla padre
     * @return {@link toba_relacion_entre_tablas toba_relacion_entre_tablas}
     * @ignore 
     */    
    
function get_relacion_con_padre($id_tabla_padre)
    {
        return 
$this->_relaciones_con_padres[$id_tabla_padre];    
    }
    
    
/**
     * Informa a la tabla que existe una tabla hija de la actual
     * @param toba_relacion_entre_tablas $relacion
     * @ignore 
     */    
    
function agregar_relacion_con_hijo($relacion$id_hijo)
    {
        
$this->_relaciones_con_hijos[$id_hijo] = $relacion;
    }

    
/*
        ***  Notificaciones  ***
    */

    
private function notificar_contenedor($evento$param1=null$param2=null)
    {
        if(isset(
$this->controlador)){
            
//$this->_contenedor->registrar_evento($this->_id, $evento, $param1, $param2);
        
}
    }

    
/**
     * Aviso a las relacion padres que el componente HIJO se CARGO
     * @ignore 
     */
    
function notificar_padres_carga($reg_hijos=null)
    {
        if(isset(
$this->_relaciones_con_padres)){
            foreach (
$this->_relaciones_con_padres as $relacion) {
                
$relacion->evt__carga_hijo($reg_hijos);
            }
        }
    }

    
/**
     * Aviso a las relaciones hijas que el componente PADRE sincrozo sus actualizaciones
     * @ignore 
     */
    
function notificar_hijos_sincronizacion($filas=array())
    {
        if(isset(
$this->_relaciones_con_hijos)){
            foreach (
$this->_relaciones_con_hijos as $relacion) {
                
$relacion->evt__sincronizacion_padre($filas);
            }
        }
    }


    
/**
     * Retorna la {@link toba_datos_relacion relacion} que contiene a esta tabla, si existe
     * @return toba_datos_relacion
     */
    
function get_relacion()
    {
        if (isset(
$this->controlador)) {
            return 
$this->controlador;        
        }
    }

    
/**
     * Retorna un objeto en el cual se puede realizar busquedas complejas de registros en memoria
     * @return toba_datos_busqueda
     */
    
function nueva_busqueda()
    {
        return new 
toba_datos_busqueda($this->controlador$this);        
    }

    
//-------------------------------------------------------------------------------
    //-- Preguntas BASICAS
    //-------------------------------------------------------------------------------

    /**
     *    Retorna las columnas que son claves en la tabla
     */
    
function get_clave()
    {
        if (empty(
$this->_clave)) {
            return 
null;
        }else{
            return 
$this->_clave;
        }
    }
    
    
/**
     * Retorna el valor de la clave para un fila dada
     * @param mixed $id_fila Id. interno de la fila
     * @return array Valores de las claves para esta fila, en formato RecordSet
     */
    
function get_clave_valor($id_fila)
    {
        
$temp = array();
        foreach( 
$this->_clave as $columna ){
            
$temp[$columna] = $this->get_fila_columna($id_fila$columna);
        }
        return 
$temp;
    }

    
/**
     * Retorna la cantidad maxima de filas que puede contener la tabla (si existe tal restriccion)
     * @return integer, 0 si no hay tope
     */
    
function get_tope_max_filas()
    {
        return 
$this->_tope_max_filas;    
    }


    
/**
     * Retorna la cantidad minima de fila que debe contener la tabla (si existe tal restriccion)
     * @return integer, 0 si no hay tope
     */    
    
function get_tope_min_filas()
    {
        return 
$this->_tope_min_filas;    
    }

    
/**
     * Retorna la cantidad de filas que sufrieron cambios desde la carga, y por lo tanto se van a sincronizar
     * @return integer 
     */
    
function get_cantidad_filas_a_sincronizar()
    {
        
$cantidad 0;
        foreach(
array_keys($this->_cambios) as $fila){
            if( (
$this->_cambios[$fila]['estado'] == "d") ||
                (
$this->_cambios[$fila]['estado'] == "i") ||
                (
$this->_cambios[$fila]['estado'] == "u") ){
                
$cantidad++;
            }
        }
        return 
$cantidad;
    }

    
/**
     * Retorna lasfilas que sufrieron cambios desde la carga
     * @param array $cambios Combinación de tipos de cambio a buscar: d, i o u  (por defecto los tres)
     * @return array Ids. internos
     */
    
function get_id_filas_a_sincronizar$cambios=array("d","i","u") )
    {
        
$ids = array();
        foreach(
array_keys($this->_cambios) as $fila){
            if( 
in_array($this->_cambios[$fila]['estado'], $cambios) ){
                
$ids[] = $fila;
            }
        }
        return 
$ids;
    }

    
/**
     * Devuelve las fks que asocian a las tablas extendidas
     * @return array
     */
    
function get_fks()
    {
        return 
$this->_info_fks;
    }

    
//-------------------------------------------------------------------------------
    //-- Configuracion
    //-------------------------------------------------------------------------------

    /**
     * Cambia la cantidad maxima de filas que puede contener la tabla
     * @param integer $cantidad 0 si no hay tope
     */    
    
function set_tope_max_filas($cantidad)
    {
        if (
$cantidad == '')
            
$cantidad 0;        
        if(
is_numeric($cantidad) && $cantidad >= 0){
            
$this->_tope_max_filas $cantidad;    
            if (
$cantidad != 1) {
                
$this->set_es_unico_registro(false);    
            }
        }else{
            throw new 
toba_error_def("El valor especificado en el TOPE MAXIMO de registros es incorrecto");
        }
    }

    
/**
     * Cambia la cantidad mínima de filas que debe contener la tabla
     * @param integer $cantidad 0 si no hay tope
     */        
    
function set_tope_min_filas($cantidad)
    {
        if (
$cantidad == '')
            
$cantidad 0;
        if(
is_numeric($cantidad) && $cantidad >= 0){
            
$this->_tope_min_filas $cantidad;
        }else{
            throw new 
toba_error_def("El valor especificado en el TOPE MINIMO de registros es incorrecto");
        }
    }

    
/**
     * Indica una combinacion de columnas cuyos valores no deben duplicarse (similar a un unique de sql)
     */
    
function set_no_duplicado$columnas )
    {
        
$this->_no_duplicado[] = $columnas;
    }

    
/**
     * Indica que la tabla maneja un único registro en memoria, habilitando la api get/set
     * @param boolean $unico
     */
    
function set_es_unico_registro($unico)
    {
        
$this->_es_unico_registro $unico;    
    }
    
    
//-------------------------------------------------------------------------------
    //-- MANEJO DEL CURSOR INTERNO---------------------------------------------------
    //-------------------------------------------------------------------------------
    
    /**
     * Fija el cursor en una fila dada
     * Cuando la tabla tiene un cursor muchas de sus operaciones empiezan a tratar a esta fila como la única 
     * y sus tablas padres e hijas también. Por ejemplo al pedir las filas de la tabla hija solo retorna aquellas filas hijas del registro cursor de la tabla padre.
     * @param mixed $id Id. interno de la fila
     */
    
function set_cursor($id)
    {
        
$id $this->normalizar_id($id);
        if( 
$this->existe_fila($id) ){
            
$this->_cursor_original = isset($this->_cursor) ? $this->_cursor null;
            
$this->_cursor $id;    
            
$this->log("Nuevo cursor '{$this->_cursor}' en reemplazo del anterior '{$this->_cursor_original}'");
        }else{
            throw new 
toba_error_def($this->get_txt() . "La fila '$id' no es valida");
        }
    }    
    
    
/**
     * Deshace el ultimo seteo de cursor
     */
    
function restaurar_cursor()
    {
        
$this->_cursor $this->_cursor_original;
        
$this->log("Se restaura el cursor '{$this->_cursor_original}'");        
    }

    
    
/**
     * Asegura que el cursor no se encuentre posicionado en ninguna fila específica
     */
    
function resetear_cursor()
    {
        unset(
$this->_cursor);
        
$this->log("Se resetea el cursor");                
    }
    
    
/**
     * Retorna el Id. interno de la fila donde se encuentra actualmente el cursor de la tabla
     * @return mixed
     */
    
function get_cursor()
    {
        if(isset(
$this->_cursor)){
            return 
$this->_cursor;
        }    
    }

    
/**
     * Hay una fila seleccionada por el cursor?
     */
    
function hay_cursor()
    {
        return isset(
$this->_cursor);
    }
    
    
//-------------------------------------------------------------------------------
    //-- ACCESO a FILAS   -----------------------------------------------------------
    //-------------------------------------------------------------------------------

    /**
     * Retorna el conjunto de filas que respeta las condiciones dadas
     * Por defecto la búsqueda es afectada por la presencia de cursores en las tablas padres.
     * @param array $condiciones Se utiliza este arreglo campo=>valor y se retornan los registros que cumplen (con condicion de igualdad) con estas restricciones. El valor no puede ser NULL porque siempre da falso
     * @param boolean $usar_id_fila Hace que las claves del array resultante sean
     * las claves internas del datos_tabla. Sino se usa una clave posicional y
     * la clave viaja en la columna apex_datos_clave_fila
     * @param boolean $usar_cursores Este conjunto de filas es afectado por la presencia de cursores en las tablas padres. 
     * @return array Formato tipo RecordSet
     */
    
function get_filas($condiciones=null$usar_id_fila=false$usar_cursores=true)
    {
        
$datos = array();
        
$a 0;
        foreach( 
$this->get_id_fila_condicion($condiciones$usar_cursores) as $id_fila ) {
            if(
$usar_id_fila){
                
$datos[$id_fila] = $this->_datos[$id_fila];
            }else{
                
$datos[$a] = $this->_datos[$id_fila];
                
//esta columna indica cual fue la clave del registro
                
$datos[$a][apex_datos_clave_fila] = $id_fila;
            }
            
$a++;
        }        
        return 
$datos;
    }
    
    
/**
     * Retorna los ids de todas las filas (sin eliminar) de esta tabla
     * @param boolean $usar_cursores Este conjunto de filas es afectado por la presencia de cursores en las tablas padres
     * @return array()
     * @todo Se podría optimizar este método para no recaer en tantos recorridos
     */
    
function get_id_filas($usar_cursores=true)
    {
        
$coincidencias = array();
        foreach(
array_keys($this->_cambios) as $id_fila){
            if(
$this->_cambios[$id_fila]['estado']!="d"){
                
$coincidencias[] = $id_fila;
            }
        }
        if (
$usar_cursores) {
            
//Si algún padre tiene un cursor posicionado, 
            //se restringe a solo las filas que son hijas de esos cursores
            
foreach ($this->_relaciones_con_padres as $id => $rel_padre) {
                
$coincidencias $rel_padre->filtrar_filas_hijas($coincidencias);
            }
        }
        return 
$coincidencias;        
    }
    
    
/**
     * Retorna los ids de todas las filas (sin eliminar) de esta tabla
     * @param boolean $usar_cursores Este conjunto de filas es afectado por la presencia de cursores en las tablas padres
     * @return array()
     * @todo Se podría optimizar este método para no recaer en tantos recorridos
     */
    
function get_id_filas_filtradas_por_cursor($incluir_eliminados=false)
    {
        if(
$this->hay_cursor()){
            return array( 
$this->get_cursor() );
        } else {
            
$coincidencias = array();
            foreach(
array_keys($this->_cambios) as $id_fila){
                if(
$incluir_eliminados || $this->_cambios[$id_fila]['estado']!="d"){
                    
$coincidencias[] = $id_fila;
                }
            }
            foreach (
$this->_relaciones_con_padres as $id => $rel_padre) {
                
$coincidencias $rel_padre->filtrar_filas_hijas($coincidencias$incluir_eliminados);
            }
            return 
$coincidencias;        
        }
    }


    
/**
     * Retorna los padres de un conjunto de registros especificos
     */
    
function get_id_padres($ids_propios$tabla_padre)
    {
        
$salida = array();
        foreach (
$ids_propios as $id_propio) {
            
$id_padre $this->get_id_fila_padre($tabla_padre$id_propio);
            if (
$id_padre !== null) {
                
$salida[] = $id_padre;    
            }
        }
        return 
array_unique($salida);
    }
    
    
/**
    * Busca en una tabla padre el id de fila padre que corresponde a la fila hija especificada
    */
    
function get_id_fila_padre($tabla_padre$id_fila)
    {
        
$id_fila $this->normalizar_id($id_fila);
        if(!isset(
$this->_relaciones_con_padres[$tabla_padre])) {
            throw new 
toba_error_def("La tabla padre '$tabla_padre' no existe");    
        }
        return 
$this->_relaciones_con_padres[$tabla_padre]->get_id_padre($id_fila);
    }
    
    
    
/**
     * Busca los registros en memoria que cumplen una condicion.
     * Solo se chequea la condicion de igualdad. No se chequean tipos
     * @param array $condiciones Asociativo de campo => valor. El valor no puede ser NULL porque siempre da falso
     *              Para condiciones más complejas (no solo igualdad) puede ser array($columna, $condicion, $valor), 
     *                 por ejemplo array(array('id_persona','>=',10),...)
     * @param boolean $usar_cursores Este conjunto de filas es afectado por la presencia de cursores en las tablas padres
     * @return array Ids. internos de las filas, pueden no estar numerado correlativamente
     */    
    
function get_id_fila_condicion($condiciones=null$usar_cursores=true)
    {    
        
//En principio las coincidencias son todas las filas
        
$coincidencias $this->get_id_filas($usar_cursores);
        
//Si hay condiciones, se filtran estas filas
        
if(isset($condiciones)){
            if(!
is_array($condiciones)){
                throw new 
toba_error("Las condiciones de filtrado deben ser un array asociativo");
            }
            
//Controlo que todas los campos que se utilizan para el filtrado existan
            /*foreach( array_keys($condiciones) as $columna){

            }*/
            
foreach($coincidencias as $pos => $id_fila){
                
//Verifico las condiciones
                
foreach( array_keys($condiciones) as $campo){
                    if (
is_array($condiciones[$campo])) {
                        list(
$columna$operador$valor) = $condiciones[$campo];
                    } else {
                        
$columna $campo;
                        
$operador '==';                        
                        
$valor $condiciones[$campo];
                    }                    
                    if( !isset(
$this->_columnas[$columna]) ){
                        throw new 
toba_error_def("El campo '$columna' no existe. No es posible filtrar por dicho campo");
                    }
                    if(!isset(
$this->_datos[$id_fila][$columna])) {
                        
// Es posible que una fila no posea una columa. Ej: una nueva fila no tiene la clave si esta es una secuencia.
                        // Si el valor no existe, considero que la comparacion con esa fila da falso (* != NULL)
                        
unset($coincidencias[$pos]);
                        break;
                    } else {
                        if (! 
comparar($this->_datos[$id_fila][$columna], $operador$valor)) {
                            
//Se filtra la fila porque no cumple las condiciones
                            
unset($coincidencias[$pos]);
                            break;
                        }
                    }
                }
            }
        }
        return 
array_values($coincidencias);
    }

    
/**
     * Retorna el contenido de una fila, a partir de su clave interna
     * @param mixed $id Id. interno de la fila en memoria
     * @return array columna => valor. En caso de no existir la fila retorna NULL
     */
    
function get_fila($id)
    {
        
$id $this->normalizar_id($id);
        if(isset(
$this->_datos[$id])){
            
$temp $this->_datos[$id];
            
$temp[apex_datos_clave_fila] = $id;    //incorporo el ID del dbr
            
return $temp;
        }else{
            return 
null;
            
//throw new toba_error("Se solicito un registro incorrecto");
        
}
    }

    
/**
     * Retorna el valor de una columna en una fila dada
     * @param mixed $id Id. interno de la fila
     * @param string $columna Nombre de la columna
     * @return mixed En caso de no existir, retorna NULL
     */
    
function get_fila_columna($id$columna)
    {
        
$id $this->normalizar_id($id);
        if(isset(
$this->_datos[$id][$columna])){
            return  
$this->_datos[$id][$columna];
        }else{
            return 
null;
        }
    }
    
    
/**
     * Retorna los valores de una columna específica
     * El conjunto de filas utilizado es afectado por la presencia de cursores en las tablas padres
     * @param string $columna Nombre del campo o columna
     * @return array Arreglo plano de valores
     */
    
function get_valores_columna($columna)
    {
        
$temp = array();
        foreach(
$this->get_id_filas() as $fila){
            
$temp[] = $this->_datos[$fila][$columna];
        }
        return 
$temp;
    }
    
    
/**
     * Retorna el valor de la columna de la fila actualmente seleccionada como cursor
     * @param string $columna Id. de la columna que contiene el valor a retornar
     * @return mixed NULL si no cursor o no hay filas
     */    
    
function get_columna($columna)
    {
        if (
$this->get_cantidad_filas() == 0) {
            return 
null;
        } elseif (
$this->hay_cursor()) {
            return 
$this->get_fila_columna($this->get_cursor(), $columna);
        } else {
            throw new 
toba_error_def("No hay posicionado un cursor en la tabla, no es posible determinar la fila actual");
        }        
    }
    
    
/**
     * Cantidad de filas que tiene la tabla en memoria
     * El conjunto de filas utilizado es afectado por la presencia de cursores en las tablas padres
     * @return integer
     */
    
function get_cantidad_filas($usar_cursores=true)
    {
        return 
count($this->get_id_filas($usar_cursores));
    }
    
    
/**
     * Existe una determina fila? (la fila puede estar marcada como para borrar)
     * @param mixed $id Id. interno de la fila
     * @return boolean
     */
    
function existe_fila($id)
    {
        
$id $this->normalizar_id($id);
        if(! isset(
$this->_datos[$id]) ){
            return 
false;            
        }
        if(
$this->_cambios[$id]['estado']=="d"){
            return 
false;
        }
        return 
true;
    }
    
    
/**
     * Busca los registros en memoria que cumplen una condicion.
     * Solo se chequea la condicion de igualdad. No se chequean tipos
     * @param array $condiciones Asociativo de campo => valor.
     *              Para condiciones más complejas (no solo igualdad) puede ser array($columna, $condicion, $valor), 
     *                 por ejemplo array(array('id_persona','>=',10),...)
     * @param boolean $usar_cursores Este conjunto de filas es afectado por la presencia de cursores en las tablas padres    
     */
    
function existe_fila_condicion($condiciones$usar_cursores true)
    {
        
$ids $this->get_id_fila_condicion($condiciones$usar_cursores);
        return !empty(
$ids);
    }

    
/**
     * Valida un id interno y a la vez permite aceptarlo como parte de un arreglo en
     * la columna apex_datos_clave_fila
     * @ignore 
     */
    
protected function normalizar_id($id)
    {
        if(!
is_array($id)){
            return 
$id;    
        }else{
            if(isset(
$id[apex_datos_clave_fila])){
                return 
$id[apex_datos_clave_fila];
            }
        }
        throw new 
toba_error_def($this->get_txt() . ' La clave tiene un formato incorrecto.');
    }

    
//-------------------------------------------------------------------------------
    //-- ALTERACION de FILAS  ------------------------------------------------------
    //-------------------------------------------------------------------------------

    /**
     * Crea una nueva fila en la tabla en memoria
     *
     * @param array $fila Asociativo campo=>valor a insertar
     * @param mixed $ids_padres Asociativo padre =>id de las filas padres de esta nueva fila, 
     *                           en caso de que no se brinde, se utilizan los cursores actuales en estas tablas padres
     * @param integer $id_nuevo Opcional. Id interno de la nueva fila, si no se especifica (recomendado)
     *                                 Se utiliza el proximo id interno.
     * @return mixed Id. interno de la fila creada
     */
    
function nueva_fila($fila=array(), $ids_padres=null$id_nuevo=null)
    {        
        
$this->notificar_contenedor("ins"$fila);
        
//Saco el campo que indica la posicion del registro
        
if(isset($fila[apex_datos_clave_fila])) unset($fila[apex_datos_clave_fila]);
        if(isset(
$fila[apex_ei_analisis_fila])) unset($fila[apex_ei_analisis_fila]);
        
        
$this->validar_fila($fila);
        
//SI existen columnas externas, completo la fila con las mismas
        
if($this->_posee_columnas_ext){
            
$campos_externos $this->persistidor()->completar_campos_externos_fila($fila,"ins");
            foreach(
$campos_externos as $id => $valor) {
                
$fila[$id] = $valor;
            }
        }
        
        
//---Se le asigna un id a la fila
        
if (!isset($id_nuevo) || $id_nuevo $this->_proxima_fila) {
            
$id_nuevo $this->_proxima_fila;
        }
        
$this->_proxima_fila $id_nuevo 1;
        
//Se notifica a las relaciones del alta
        
foreach ($this->_relaciones_con_padres as $padre => $relacion) {
            
$id_padre null;
            if (isset(
$ids_padres[$padre])) {
                
$id_padre $ids_padres[$padre];
            }
            
$relacion->asociar_fila_con_padre($id_nuevo$id_padre);                            
        }
        
        
//Se agrega la fila
        
$this->_datos[$id_nuevo] = $fila;
        
$this->registrar_cambio($id_nuevo,"i");
        
        return 
$id_nuevo;
    }

    
/**
     * Modifica los valores de una fila de la tabla en memoria
     * Solo se modifican los valores de las columnas enviadas y que realmente cambien el valor de la fila.
     * @param mixed $id Id. interno de la fila a modificar
     * @param array $fila Contenido de la fila, en formato columna=>valor, puede ser incompleto
     * @param array $nuevos_padres Arreglo (id_tabla_padre => $id_fila_padre, ....), solo se cambian los padres que se pasan por parámetros
     *                 El resto de los padres sigue con la asociación anterior
     * @return mixed Id. interno de la fila modificada
     */
    
function modificar_fila($id$fila$nuevos_padres=null)
    {
        
$id $this->normalizar_id($id);
        if (!
$this->existe_fila($id)){
            
$mensaje $this->get_txt() . " MODIFICAR. No existe un registro con el INDICE indicado ($id)";
            
toba::logger()->error($mensaje);
            throw new 
toba_error_def($mensaje);
        }
        
//Saco el campo que indica la posicion del registro
        
if(isset($fila[apex_datos_clave_fila])) unset($fila[apex_datos_clave_fila]);
        if(isset(
$fila[apex_ei_analisis_fila])) unset($fila[apex_ei_analisis_fila]);      
        
        
$this->validar_fila($fila$id);
        
$this->notificar_contenedor("pre_modificar"$fila$id);
        
        
//Actualizo los valores
        
$alguno_modificado false;
        foreach(
array_keys($fila) as $clave){
            
$modificar $this->es_campo_modificado($clave$id$fila);
            if (
$modificar) {
                
$alguno_modificado true;
                
$this->_datos[$id][$clave] = $fila[$clave];
            }
        }
        
//--- Esto evita propagar cambios que en realidad no sucedieron
        
if ($alguno_modificado) {
            if(
$this->_cambios[$id]['estado']!="i"){
                
$this->registrar_cambio($id,"u");
            }
            
            
/*
                Como los campos externos pueden necesitar un campo que no entrego la
                interfaz, primero actualizo los valores y despues tomo la fila y la
                proceso con la actualizacion de campos externos
            */
            //Si la tabla posee campos externos, le pido la nueva fila al persistidor
            
if($this->_posee_columnas_ext){
                
$campos_externos $this->persistidor()->completar_campos_externos_fila($this->_datos[$id],"upd");
                foreach(
$campos_externos as $clave => $valor){
                    
$this->_datos[$id][$clave] = $valor;
                }
            }
        }
        
$this->notificar_contenedor("post_modificar"$fila$id);
        if (isset(
$nuevos_padres)) {
            
$this->cambiar_padre_fila($id$nuevos_padres);
        }
        return 
$id;
    }

    
/**
     * Cambia los padres de una fila
     * @param mixed $id_fila 
     * @param array $nuevos_padres Arreglo (id_tabla_padre => $id_fila_padre, ....), solo se cambian los padres que se pasan por parámetros
     *                 El resto de los padres sigue con la asociación anterior
     */
    
function cambiar_padre_fila($id_fila$nuevos_padres)
    {
        
$id $this->normalizar_id($id_fila);        
        if (!
$this->existe_fila($id)){
            
$mensaje $this->get_txt() . " CAMBIAR PADRE. No existe un registro con el INDICE indicado ($id)";
            
toba::logger()->error($mensaje);
            throw new 
toba_error_def($mensaje);
        }
        
$cambio_padre false;
        foreach (
$nuevos_padres as $tabla_padre => $id_padre) {
            if (!isset(
$this->_relaciones_con_padres[$tabla_padre])) {
                
$mensaje $this->get_txt() . " CAMBIAR PADRE. No existe una relación padre $tabla_padre.";
                throw new 
toba_error_def($mensaje);
            }
            if (
$this->_relaciones_con_padres[$tabla_padre]->set_padre($id_fila$id_padre)) {
                
$cambio_padre true;    
            }
        }
        
//-- Si algun padre efectivamente cambio, tengo que marcar al registro como actualizado
        
if ($cambio_padre) {
            if(
$this->_cambios[$id_fila]['estado']!="i"){
                
$this->registrar_cambio($id_fila,"u");
            }
        }
    }
    
    
/**
     * Elimina una fila de la tabla en memoria
     * En caso de que la fila sea el cursor actual de la tabla, este ultimo se resetea
     * @param mixed $id Id. interno de la fila a eliminar
     * @return Id. interno de la fila eliminada
     */
    
function eliminar_fila($id)
    {
        
$id $this->normalizar_id($id);
        if (!
$this->existe_fila($id)) {
            
$mensaje $this->get_txt() . " ELIMINAR. No existe un registro con el INDICE indicado ($id)";
            
toba::logger()->error($mensaje);
            throw new 
toba_error_def($mensaje);
        }
        if ( 
$this->get_cursor() == $id ) { 
             
$this->resetear_cursor();        
        }
         
$this->notificar_contenedor("pre_eliminar"$id);
        
//Se notifica la eliminación a las relaciones
        
foreach ($this->_relaciones_con_hijos as $rel) {
            
$rel->evt__eliminacion_fila_padre($id);
        }
        foreach ( 
$this->_relaciones_con_padres as $rel) {
            
$rel->evt__eliminacion_fila_hijo($id);            
        }
        if(
$this->_cambios[$id]['estado']=="i"){
            unset(
$this->_cambios[$id]);
            unset(
$this->_datos[$id]);
        }else{
            
$this->registrar_cambio($id,"d");
        }
        
$this->notificar_contenedor("post_eliminar"$id);
        return 
$id;
    }

    
/**
     * Elimina todas las filas de la tabla en memoria
     * @param boolean $con_cursores Tiene en cuenta los cursores del padre para afectar solo sus filas hijas, por defecto utiliza cursores. 
     */
    
function eliminar_filas($con_cursores true)
    {
        foreach(
$this->get_id_filas($con_cursores) as $fila) {
            
$this->eliminar_fila($fila);
        }
    }

    
/**
     * Cambia el valor de una columna de una fila especifica
     *
     * @param mixed $id Id. interno de la fila de la tabla en memoria
     * @param string $columna Columna o campo de la fila
     * @param mixed $valor Nuevo valor
     */
    
function set_fila_columna_valor($id$columna$valor)
    {
        
$id $this->normalizar_id($id);
        if( 
$this->existe_fila($id) ){
            if( isset(
$this->_columnas[$columna]) ){
                
$this->modificar_fila($id, array($columna => $valor));
            }else{
                throw new 
toba_error_def("La columna '$columna' no es valida");
            }
        }else{
            throw new 
toba_error_def("La fila '$id' no es valida");
        }
    }

    
/**
     * Cambia el valor de una columna en todas las filas
     * 
     * @param string $columna Nombre de la columna a modificar
     * @param mixed $valor Nuevo valor comun a toda la columna
     * @param boolean $con_cursores Tiene en cuenta los cursores del padre para afectar sus filas hijas, por defecto no
     */
    
function set_columna_valor($columna$valor$con_cursores=false)
    {
        if(! isset(
$this->_columnas[$columna]) ) { 
            throw new 
toba_error_def("La columna '$columna' no es valida");
        }
        foreach(
$this->get_id_filas($con_cursores) as $fila) {
            
$this->modificar_fila($fila, array($columna => $valor));
        }        
    }

    
/**
     * Procesa los cambios masivos de filas
     * 
     * El id de la fila se asume que la key del registro o la columna apex_datos_clave_fila
     * Para procesar es necesario indicar el estado de cada fila utilizando una columna referenciada con la constante 'apex_ei_analisis_fila' los valores pueden ser:
     *  - 'A': Alta
     *  - 'B': Baja
     *  - 'M': Modificacion
     *
     * @param array $filas Filas en formato RecordSet, cada registro debe contener un valor para la constante apex_ei_analisis_fila
     * @param mixed $ids_padres Asociativo padre =>id de las filas padres de esta nueva fila, 
     *                           en caso de que no se brinde, se utilizan los cursores actuales en estas tablas padres
     */
    
function procesar_filas($filas$ids_padres=null)
    {
        
toba_asercion::es_array($filas,"toba_datos_tabla - El parametro no es un array.");
        
//--- Controlo estructura
        
foreach(array_keys($filas) as $id){
            if(!isset(
$filas[$id][apex_ei_analisis_fila])){
                throw new 
toba_error_def("Para procesar un conjunto de registros es necesario indicar el estado ".
                                    
"de cada uno utilizando una columna referenciada con la constante 'apex_ei_analisis_fila'.
                                    Si los datos provienen de un ML, active la opción de analizar filas."
);
            }
        }
        
//--- Se asume que el id de la fila es la key del registro o la columna apex_datos_clave_fila. 
        
foreach ($filas as $id => $fila) {
            
$id_explicito false;
            if (isset(
$fila[apex_datos_clave_fila])) {
                
$id $fila[apex_datos_clave_fila];
                
$id_explicito true;
            }    
            
$accion $fila[apex_ei_analisis_fila];
            unset(
$fila[apex_ei_analisis_fila]);
            switch(
$accion){
                case 
"A":
                    
//--- Si el ML notifico explicitamente el id, este es el id de la nueva fila, sino usa el mecanismo interno
                    
$nuevo_id = ($id_explicito) ? $id null;
                    
$this->nueva_fila($fila$ids_padres$nuevo_id);
                    break;    
                case 
"B":
                    
$this->eliminar_fila($id);
                    break;    
                case 
"M":
                    
$this->modificar_fila($id$fila);
                    break;    
            }
        }
    }

    
//-------------------------------------------------------------------------------
    //-- Simplificación sobre una sola línea
    //-------------------------------------------------------------------------------

    /**
     * Cambia el contenido de la fila donde se encuentra el cursor interno
     * Si la tabla se definio admitiendo a lo sumo un registro, este cursor se posiciona automáticamente en la carga, sino se debe explicitar con el método set_cursor
     * En caso que no existan filas, se crea una nueva y se posiciona el cursor en ella
     * Si la fila es null, se borra la fila actual
     *
     * @param array $fila Contenido total o parcial de la fila a crear o modificar (si es null borra la fila actual)
     */
    
function set($fila)
    {
        if(
$this->hay_cursor()){
            if (isset(
$fila)) {
                
$this->modificar_fila($this->get_cursor(), $fila);
            } else {
                
$this->eliminar_fila($this->get_cursor());
            }
        } else {
            if (isset(
$fila)) {
                
$id $this->nueva_fila($fila);
                
$this->set_cursor($id);
            }
        }
    }
    
    
/**
     * Retorna el contenido de la fila donde se encuentra posicionado el cursor interno
     * Si la tabla se definio admitiendo a lo sumo un registro, este cursor se posiciona automáticamente en la carga, sino se debe explicitar con el método set_cursor
     * En caso de que no haya registros retorna NULL
     */
    
function get()
    {
        if (
$this->get_cantidad_filas() == 0) {
            return 
null;
        } elseif (
$this->hay_cursor()) {
            return 
$this->get_fila($this->get_cursor());
        } else {
            throw new 
toba_error_def("No hay posicionado un cursor en la tabla, no es posible determinar la fila actual");
        }
    }


    
//---------------------------------------------------------------------------
    //-- Trabajo con campos BLOBs  ----------------------------------------------
    //---------------------------------------------------------------------------    
    
    /**
     * Almacena un 'file pointer' en un campo binario o blob de la tabla. 
     * @param string $columna Nombre de la columna binaria-blob
     * @param resource $blob file pointer o null en caso de querer borrar el valor
     * @param mixed $id_fila Id. interno de la fila que contiene la columna, en caso de ser vacio se utiliza el cursor
     */
    
function set_blob($columna$blob$id_fila=null)
    {
        if (isset(
$blob) && ! is_resource($blob)) {
            throw new 
toba_error_def("Las columnas binarias o BLOB esperan un 'resource', producto generalmente de un 'fopen' del archivo a subir a la base");    
        }
        if (! isset(
$id_fila)) {
            if (
$this->hay_cursor()){
                
$id_fila $this->get_cursor();
            } else {
                throw new 
toba_error_def("No hay posicionado un cursor en la tabla, no es posible determinar la fila actual");    
            }
        }
        
//Borra algïñun cache previo
        
if (isset($this->_blobs[$id_fila][$columna]['path']) && $this->_blobs[$id_fila][$columna]['path'] != '') {
            
$path $this->_blobs[$id_fila][$columna]['path'];
            if (
file_exists($path)) {
                
unlink($path);
            }
        }
        
        
$this->_blobs[$id_fila][$columna] = array('fp'=>$blob'path'=>'',  'modificado' => true);
        if(isset(
$this->_cambios[$id_fila]['estado']) && $this->_cambios[$id_fila]['estado']!="i"){
            
$this->registrar_cambio($id_fila,"u");
        }
    }
    
    
/**
     * Retorna un 'file pointer' apuntando al campo binario o blob de la tabla.
     *
     * @param string $columna Nombre de la columna binaria-blob
     * @param mixed $id_fila Id. interno de la fila que contiene la columna, en caso de ser vacio se utiliza el cursor
     * @return resource
     */
    
function get_blob($columna$id_fila=null)
    {
        if (! isset(
$id_fila)) {
            if (
$this->get_cantidad_filas() == 0) {
                return 
null;
            } elseif (
$this->hay_cursor()) {
                
$id_fila $this->get_cursor();
            } else {
                throw new 
toba_error_def("No hay posicionado un cursor en la tabla, no es posible determinar la fila actual");
            }
        }
        if (!isset(
$this->_blobs[$id_fila][$columna])) {
            
//-- Si no tiene el dato y es una fila nueva, no hay nada 
            
if ($this->_cambios[$id_fila]['estado'] == "i") {
                return 
null;
            }
            
//-- Carga peresoza del file_pointer
            
$fp $this->persistidor()->consultar_columna_blob($id_fila$columna);
            
$this->_blobs[$id_fila][$columna] = array('fp' => $fp'path' => '''modificado' => false);
        }
        
$fp $this->_blobs[$id_fila][$columna]['fp'];
        
$path $this->_blobs[$id_fila][$columna]['path'];
        if (!
is_resource($fp) && $path != '') {
            
//Si no es un recurso el $fp, se carga desde el path previamente cargado
            
$fp fopen($path'rb');
            
$this->_blobs[$id_fila][$columna]['fp'] = $fp;
        }
        return 
$fp;
    }
    
    
/**
     * Permite obtener el fp de un blob que se haya modificado en esta transaccion
     * @ignore
     */
    
function _get_blob_transaccion($id_registro$col)
    {
        
//-- Si no esta seteado, no se cargo ni modifico
        
if (!isset($this->_blobs[$id_registro][$col])) {
            return 
null;
        } elseif (
$this->_blobs[$id_registro][$col]['modificado']) {
            
//--Hay que actualizar el BLOB
            
$fp $this->_blobs[$id_registro][$col]['fp'];
            
$path $this->_blobs[$id_registro][$col]['path'];
            if (! 
is_resource($fp)) {
                if (
$path != '') {
                    
$fp fopen($path'rb');
                    if (! 
is_resource($fp)) {
                        throw new 
toba_error_def("No fue posible recuperar el campo '$col' de la fila '$id_registro' desde archivo temporal '$fp'");
                    }
                } else {
                    
// Quiere decir que explicitamente se lo hizo nulo
                    
return false;
                }
            }
            return 
$fp;

        } else {
            return 
null;
        }        
    }
    
    
//-------------------------------------------------------------------------------
    //-- VALIDACION en LINEA
    //-------------------------------------------------------------------------------

    /**
     * Valida un registro durante el procesamiento
     */
    
private function validar_fila($fila$id=null)
    {
        if(!
is_array($fila)){
            throw new 
toba_error_def($this->get_txt() . ' La fila debe ser una array');    
        }
        
$this->evt__validar_ingreso($fila$id);
        
$this->control_estructura_fila($fila);
    }

    
/**
     * Ventana de validacion que se invoca cuando se crea o modifica una fila en memoria. Lanzar una excepcion en caso de error
     * @param array $fila Datos de la fila
     * @param mixed $id Id. interno de la fila, si tiene (en el caso modificacion de la fila)
     * 
     * @ventana
     */
    
protected function evt__validar_ingreso($fila$id=null){}

    
//-------------------------------------------------------------------------------

    /**
     * Controla que los campos del registro existan
     * @ignore 
     */
    
protected function control_estructura_fila($fila)
    {
        foreach(
$fila as $campo => $valor){
            
//SI el registro no esta en la lista de manipulables o en las secuencias...
            
if( !(isset($this->_columnas[$campo]))  ){
                
$mensaje $this->get_txt() . get_class($this)." El registro tiene una estructura incorrecta: El campo '$campo' "
                        
" no forma parte de la DEFINICION.";
                
toba::logger()->warning($mensaje);
            }
        }
    }
    
//-------------------------------------------------------------------------------

    /**
     * Controla que un registro no duplique los valores existentes
     */
    
private function control_valores_unicos_fila($fila$id=null)
    
//Controla que un registro no duplique los valores existentes
    
{
        if (isset(
$this->_no_duplicado)) {
            foreach(
$this->_no_duplicado as $columnas) {                //Para cada constraint de columnas
                
$this->validar_columnas_en_fila($columnas$fila$id);
            }
        }
    }

    
/**
     * Funcion que realiza la comparacion propiamente dicha de los datos
     * para el control de valores unicos. Esta separacion hace que la relacion entre
     * los distintos constraints sea un OR.. (antes era un AND)
     * @ignore
     */
    
protected function  validar_columnas_en_fila($columnas$fila$id)
    {
        foreach(
array_keys($this->_datos) as $id_fila) {                      //Recorro todos los datos cargados
             
if (! is_null($id) && ($id_fila == $id)) continue;                        //Si es la misma fila no se procesa
             
if ($this->_cambios[$id_fila]['estado'] != 'd') {                         //Si la fila no esta marcada para borrado
                
$combinacion_existente true;
                foreach(
$columnas as $columna) {                                    //Comparo los valores de las columnas del constraint
                    
if (isset($fila[$columna])) {
                        
$combinacion_existente $combinacion_existente && ($fila[$columna] == $this->_datos[$id_fila][$columna]);
                    }else{
                        return;                                        
//No existe valor para la columna, el constraint no salta
                    
}
                }
                if (
$combinacion_existente) {
                    throw new 
toba_error_validacion($this->get_txt().": Error de valores repetidos en columna '$columna'");
                }
            }
        }        
    }

    
//-------------------------------------------------------------------------------
    //-- VALIDACION global
    //-------------------------------------------------------------------------------

    /**
     * Validacion de toda la tabla necesaria previa a la sincronización
     */
    
function validar($filas=array())
    {
        
$this->control_tope_maximo_filas($this->get_cantidad_filas());        
        
$ids $this->get_id_filas_a_sincronizar( array("u","i") );
        if(isset(
$ids)){
            foreach(
$ids as $id){
                if(
count($filas)>0) {
                    if (!
in_array($id,$filas)) continue;    
                }
                
//$this->control_nulos($fila);
                
$this->evt__validar_fila$this->_datos[$id] );
                
$this->control_valores_unicos_fila($this->_datos[$id], $id);
            }
        }
        
$this->control_tope_minimo_filas();
    }
    
    
/**
     * Ventana para hacer validaciones particulares previo a la sincronización
     * El proceso puede ser abortado con un toba_error, el mensaje se muestra al usuario
     * @param array $fila Asociativo clave-valor de la fila a validar
     * 
     * @ventana
     */
    
function evt__validar_fila($fila){}

    
/*
        Controles previos a la sincronizacion
        Esto va a aca o en el AP??
    */
/*
    private function control_nulos($fila)
    //Controla que un registro posea los valores OBLIGATORIOS
    {
        $mensaje_usuario = "El elemento posee valores incompletos";
        $mensaje_programador = $this->get_txt() . " Es necesario especificar un valor para el campo: ";
        if(isset($this->_campos_no_nulo)){
            foreach($this->_campos_no_nulo as $campo){
                if(isset($fila[$campo])){
                    if((trim($fila[$campo])=="")||(trim($fila[$campo])=='NULL')){
                        toba::logger()->error($mensaje_programador . $campo);
                        throw new toba_error($mensaje_usuario . " ('$campo' se encuentra vacio)");
                    }
                }else{
                        toba::logger()->error($mensaje_programador . $campo);
                        throw new toba_error($mensaje_usuario . " ('$campo' se encuentra vacio)");
                }
            }
        }
    }
*/
    /**
     * Valida que la cantidad de filas supere el mínimo establecido
     */
    
protected function control_tope_minimo_filas()
    {
        if (
$this->_tope_min_filas != && $this->get_cantidad_filas() < $this->_tope_min_filas) {
                throw new 
toba_error_validacion("La tabla <em>{$this->_id_en_controlador}</em> requiere ingresar al menos {$this->_tope_min_filas} registro/s (se encontraron
                sólo 
{$this->get_cantidad_filas()}).");
        }
    }

    
/**
     * Valida que la cantidad de filas a crear no supere el maximo establecido
     */    
    
protected function control_tope_maximo_filas($cantidad)
    {
        if ((
$this->_tope_max_filas != 0) && ($cantidad $this->_tope_max_filas)) {
            throw new 
toba_error_validacion("No está permitido ingresar más de {$this->_tope_max_filas} registros
                                    en la tabla <em>
{$this->_id_en_controlador}</em> (se encontraron $cantidad).");
        }
    }
    

    
//-------------------------------------------------------------------------------
    //-- PERSISTENCIA  -------------------------------------------------------------
    //-------------------------------------------------------------------------------

    /**
     * Retorna el admin. de persistencia que asiste a este objeto durante la sincronización
     * @return toba_ap_tabla_db
     */
    
function persistidor()
    {
        if(!isset(
$this->_persistidor)){
            if(
$this->_info_estructura['ap']=='0'){
                
$clase $this->_info_estructura['ap_sub_clase'];
                
$include $this->_info_estructura['ap_sub_clase_archivo'];
                if( (
trim($clase) == '' ) ){
                    throw new 
toba_error_def$this->get_txt() . "Error en la definicion, falta definir la subclase");
                }
            }else{
                
$clase 'toba_'.$this->_info_estructura['ap_clase'];
                
$include $this->_info_estructura['ap_clase_archivo'];
            }
            if( ! 
class_exists($clase) ) {
                
$punto toba::puntos_montaje()->get_por_id($this->_info_estructura['punto_montaje']);
                
$path  $punto->get_path_absoluto().'/'.$include;
                require_once(
$path);
            }
            
$this->_persistidor = new $clase$this );
            if(
$this->_info_estructura['ap_modificar_claves']){
                
$this->_persistidor->activar_modificacion_clave();
            }
        }
        return 
$this->_persistidor;
    }
    
    
/**
     * @deprecated  Usar persistidor() a secas
     */
    
function get_persistidor()
    {
        return 
$this->persistidor();
    }
    


    
/**
     * Carga la tabla restringiendo POR valores especificos de campos
     * Si los datos contienen una unica fila, esta se pone como cursor de la tabla
     */
    
function cargar($clave=array())
    {
        return 
$this->persistidor()->cargar_por_clave($clave);
    }
    
    
/**
     * La tabla esta cargada con datos?
     * @return boolean
     */
    
function esta_cargada()
    {
        return 
$this->_cargada;
    }

    
/**
     * Carga la tabla en memoria con un nuevo set de datos (se borra todo estado anterior)
     * Si los datos contienen una unica fila, esta se pone como cursor de la tabla
     * @param array $datos en formato RecordSet
     */
    
function cargar_con_datos($datos)
    {
        
$this->log("Carga de datos  FILAS: " count($datos));
        
$this->_datos null;
        
//Controlo que no se haya excedido el tope de registros        
        
$this->control_tope_maximo_filas(count($datos));
        
        
$this->_datos $datos;        

        
//Genero la estructura de control de cambios
        
$this->generar_estructura_cambios();
        
//Actualizo la posicion en que hay que incorporar al proximo registro
        
$this->_proxima_fila count($this->_datos);
        
//Marco la tabla como cargada
        
$this->_cargada true;
        
//Si es una unica fila se pone como cursor de la tabla
        
if (count($datos) == && $this->_es_unico_registro) {
            
$this->_cursor 0;
        }
        
//Disparo la actulizacion de los mapeos con las tablas padres
        
$this->notificar_padres_carga();
    }

    
/**
     * Agrega a la tabla en memoria un nuevo set de datos (conservando el estado anterior). 
     * Se asume que el set de datos llega desde el mecanismo de persistencia.
     * 
     * @param array $datos en formato RecordSet
     * @param boolean $usar_cursores Los datos cargados se marcan como hijos de los cursores actuales en las tablas padre, sino son hijos del padre que tenia en la base 
     * @return array Ids. internos de los datos anexados
     */
    
function anexar_datos($datos$usar_cursores=true)
    {
        
$this->log("Anexado de datos  FILAS: " count($datos) );
        
//Controlo que no se haya excedido el tope de registros
        
$this->control_tope_maximo_filas(count($this->get_id_filas(false)) + count($datos));

        
//Agrego las filas
        
$hijos = array();
        foreach( 
$datos as $fila ) {
            
$this->_datos[$this->_proxima_fila] = $fila;
            if (
$usar_cursores) {
                
//Se notifica a las relaciones a los padres.
                
foreach ($this->_relaciones_con_padres as $padre => $relacion) {
                    
$this->log("Anexado de datos: SET mapeo $padre");
                    
$relacion->asociar_fila_con_padre($this->_proxima_filanull);
                }
            }
            
$hijos[] = $this->_proxima_fila;
            
$this->_proxima_fila++;            
        }
        
$this->regenerar_estructura_cambios($hijos);
        
//Marco la tabla como cargada
        
$this->_cargada true;
        if (! 
$usar_cursores) {
            
//Disparo la actulizacion de los mapeos con las tablas padres
            
$this->log("Mapear cursores ["implode(',',$hijos) ."] a padres.");
            
$this->notificar_padres_carga($hijos);
        }
        return 
$hijos;
    }
        
    
/**
     * Sincroniza la tabla en memoria con el medio físico a travéz del administrador de persistencia.
     *
     * @return integer Cantidad de registros modificados en el medio
     */
    
function sincronizar($usar_cursores=false)
    {
        if(
$usar_cursores) {
            
$filas $this->get_id_filas_filtradas_por_cursor(false);
            if(
$filas) {    // Si los cursores no filtran registros, no sincronizo nada
                
$this->validar($filas);
                
$modif $this->persistidor()->sincronizar($filas);
                
//Regenero la estructura que mantiene los cambios realizados
                
$this->notificar_fin_sincronizacion($filas);                
            }
        } else {
            
$this->validar();
            
$modif $this->persistidor()->sincronizar();
            
//Regenero la estructura que mantiene los cambios realizados
            
$this->notificar_fin_sincronizacion();
        }
        return 
$modif;
    }

    
/**
     * Sincroniza un conjunto de filas de la tabla en memoria con el medio físico a travéz del administrador de persistencia.
     *
     * @return integer Cantidad de registros modificados en el medio
     */
    
function sincronizar_filas($filas)
    {
        
$this->validar($filas);
        
$modif $this->persistidor()->sincronizar($filas);
        
$this->notificar_fin_sincronizacion($filas);
        return 
$modif;
    }


    
/**
     * Elimina todas las filas de la tabla en memoria y sincroniza con el medio de persistencia
     */
    
function eliminar_todo()
    {
        
//Me elimino a mi
        
$this->eliminar_filas(false);
        
//Sincronizo con la base
        
$this->persistidor()->sincronizar_eliminados();
        
$this->resetear();
    }
    
    
/**
     * @deprecated Desde 0.8.4, usar eliminar_todo()
     */
    
function eliminar()
    {
        
toba::logger()->obsoleto(__CLASS____METHOD__"0.8.4""Usar eliminar_todo");
        
$this->eliminar_todo();    
    }

    
/**
     * Deja la tabla sin carga alguna, se pierden todos los cambios realizados desde la carga
     */
    
function resetear()
    {
        
$this->log("RESET!!");
        
$this->_datos = array();
        
$this->_cargada false;
        
$this->_cambios = array();
        
$this->_proxima_fila 0;
        
$this->_where null;
        
$this->_from null;
        
//-- Borra los temporales creados
        
foreach (array_keys($this->_blobs) as $fila) {
            foreach (
array_keys($this->_blobs[$fila]) as $campo) {
                if (isset(
$this->_blobs[$fila][$campo]['path']) && $this->_blobs[$fila][$campo]['path'] != '') {
                    
$path $this->_blobs[$fila][$campo]['path'];
                    if (
file_exists($path)) {
                        
unlink($path);
                    }
                }
            }
        }
        
$this->_blobs = array();
        foreach (
$this->_relaciones_con_hijos as $rel_hijo) {
            
$rel_hijo->resetear();    
        }
        
$this->resetear_cursor();
    }

    
//-------------------------------------------------------------------------------
    //-- Comunicacion con el Administrador de Persistencia
    //-------------------------------------------------------------------------------

    /*--- Del AP a mi ---*/

    /**
     * El AP avisa que terminó la sincronización
     * @ignore 
     */
    
function notificar_fin_sincronizacion($filas=array())
    {
        
$this->regenerar_estructura_cambios($filas);
    }

    
/*--- De mi al AP ---*/

    /**
     * @ignore 
     */
    
function get_conjunto_datos_interno()
    {
        return 
$this->_datos;
    }

    
/**
     * Retorna la estructura interna que mantiene registro de las modificaciones/altas/bajas producidas en memoria
     * @ignore 
     */
    
function get_cambios()
    {
        return 
$this->_cambios;    
    }

    
/**
     * Retorna el nombre de las columnas de esta tabla
     */
    
function get_columnas()
    {
        return 
$this->_columnas;
    }
    
    
/**
     * Retorna el nombre de la {@link toba_fuente_datos fuente de datos} utilizado por este componente
     * @return string
     */
    
function get_fuente()
    {
        return 
$this->_info["fuente"];
    }

    
/**
     * Nombre de la tabla que se representa en memoria
     */
    
function get_tabla()
    {
        return 
$this->_info_estructura['tabla'];
    }

    
/**
     * Devuelve el nombre de la tabla extendida
     */
    
function get_tabla_extendida()
    {
        return 
$this->_info_estructura['tabla_ext'];
    }

    
/**
     * Retorna el schema de BD sobre el que trabaja el datos_tabla
     * @return string 
     */
    
function get_schema()
    {
        if (isset(
$this->_info_estructura['esquema'])) {
            return 
$this->_info_estructura['esquema'];
        }
        return 
null;
    }    
    
    function 
get_schema_ext()
    {
        if (isset(
$this->_info_estructura['esquema_ext'])) {
            return 
$this->_info_estructura['esquema_ext'];
        }
        return 
null;        
    }
    
    
    
/**
     * Retorna el alias utilizado para desambiguar la tabla en uniones tales como JOINs
     * Se toma el primero seteado de: el alias definido, el rol en la relación o el nombre de la tabla
     * @return string
     */
    
function get_alias()
    {
        if (isset(
$this->_info_estructura['alias'])) {
            return 
$this->_info_estructura['alias'];    
        } elseif (isset(
$this->_id_en_controlador)) {
            return 
$this->_id_en_controlador;
        } else {
            return 
$this->get_tabla();
        }
    }

    
/**
     * La tabla posee alguna columna marcada como de 'carga externa'
     * Una columna externa no participa en la sincronización posterior, pero por necesidades casi siempre estáticas
     * necesitan mantenerse junto al conjunto de datos.
     * @return boolean
     */
    
function posee_columnas_externas()
    {
        return 
$this->_posee_columnas_ext;
    }

    
//-------------------------------------------------------------------------------
    //-- Manejo de la estructura de cambios
    //-------------------------------------------------------------------------------

    /**
     * @ignore 
     */
    
protected function generar_estructura_cambios($limpiar=true$indice_datos=null)
    {
        
//Genero la estructura de control
        
if ($limpiar) {
            
$this->_cambios = array();
        }
        if (! isset(
$indice_datos)) {
            
$indice_datos array_keys($this->_datos);
        }
        foreach(
$indice_datos as $dato){
            
$this->_cambios[$dato]['estado']    = "db";
            
$this->_cambios[$dato]['clave']        = $this->get_clave_valor($dato);
            
$this->_cambios[$dato]['original']    = $this->_datos[$dato];
        }
    }
    
    
/**
     * @ignore 
     */
    
protected function regenerar_estructura_cambios($filas=array())
    {
        
//BORRO los datos eliminados
        
if(!$filas) {
            foreach(
array_keys($this->_cambios) as $cambio){
                if(
$this->_cambios[$cambio]['estado']=='d'){
                    unset(
$this->_datos[$cambio]);
                }
            }
            
$this->generar_estructura_cambios();
        } else {
            
$this->generar_estructura_cambios(false$filas);
        }
    }

    
/**
    * Determina que todas las filas de la tabla son nuevas
    * @param boolean $usar_cursores Si esta seteado, solo se marcan como nuevas las filas marcadas por el cursor
    *    
    */
    
function forzar_insercion($usar_cursores=false$filas=null)
    {
        if(
$usar_cursores) {
            
$filas $this->get_id_filas_filtradas_por_cursor();
        } else {
            if (!isset(
$filas)) {
                
$filas array_keys($this->_cambios);
            }
        }
        foreach( 
$filas as $fila) {
            
$this->log("FORZAR INSERT en FILA: $fila");
            
$this->registrar_cambio($fila"i");
        }
    }

    
/**
     * Fuerza una cambio directo a la estructura interna que mantiene registro de los cambios
     * @param mixed $fila Id. interno de la fila
     * @param string $estado
     */
    
protected function registrar_cambio($fila$estado)
    {
        
$this->_cambios[$fila]['estado'] = $estado;
    }
    
    
/**
     * Determina si los datos cargados en la tabla difieren de los datos existentes en la base al inicio de la transacción
     * @return boolean
     */    
    
function hay_cambios()
    {
        if (
$this->get_cantidad_filas_a_sincronizar() > 0) {
            
$altas_bajas $this->get_id_filas_a_sincronizar(array('d','i'));
            if (! empty(
$altas_bajas)) {
                return 
true;
            }
            foreach (
$this->get_id_filas_a_sincronizar(array('u')) as $id_fila) {
                if (
$this->hay_cambios_fila($id_fila)) {
                    return 
true;
                }
            }
        }
        return 
false;
    }

    
/**
     * Retorna verdadero si algún valor de la tabla cambio desde el inicio de la transacción
     * @return boolean
     */
    
function hay_cambios_fila($id_fila)
    {
        
$cambios $this->get_cambios_fila($id_fila);
        return ! empty(
$cambios);
    }

    
/**
     * Calcula las diferencias entre el valor original de la fila al momento de carga y el valor actual
     * @return array Asociativo campo => array('anterior' => $anterior, 'actual' => $actual)
     */
    
function get_cambios_fila($id_fila$datos_ap = array())
    {
        
$diferencias = array();
        
$datos_viejos $this->_cambios[$id_fila]['original'];
        if (empty(
$datos_ap)) {                                                //Si el administrador de persistencia no pide comparacion especifica
                
$datos_nuevos $this->_datos[$id_fila];    //utilizo los datos internos del datos_tabla.
        
} else {
                
$datos_nuevos $datos_ap;
        }
        foreach (
$this->_columnas as $campo => $col) {
            if (!
$col['externa']) {
                if (
$col['tipo'] != 'B') {
                    if (! isset(
$datos_nuevos[$campo]) && isset($datos_viejos[$campo])) {
                        
$diferencias[$campo] = array('anterior' => $datos_viejos[$campo], 'actual' => null);
                    } elseif (isset(
$datos_nuevos[$campo]) && !isset($datos_viejos[$campo])) {
                        
$diferencias[$campo] = array('anterior' => null'actual' => $datos_nuevos[$campo]);
                    } elseif (! isset(
$datos_nuevos[$campo]) && ! isset($datos_viejos[$campo])) {
                        
//No hace nada
                    
} else {
                        if (
is_bool($datos_viejos[$campo])) {
                            
$datos_viejos[$campo] = $datos_viejos[$campo] ? 0;
                        }
                        if (
is_bool($datos_nuevos[$campo])) {
                            
$datos_nuevos[$campo] = $datos_nuevos[$campo] ? 0;
                        }
                        
//--- Comparacion por igualdad estricta con un cast a string
                        
if ($this->persistidor()->get_usar_trim()) {
                            
$modificar =  (trim((string) $datos_viejos[$campo]) !== trim((string) $datos_nuevos[$campo]));                            
                        } else {
                            
$modificar =  (((string) $datos_viejos[$campo]) !== ((string) $datos_nuevos[$campo]));
                        }
                        if (
$modificar) {
                            
$diferencias[$campo] = array('anterior' => $datos_viejos[$campo], 'actual' => $datos_nuevos[$campo]);
                        }
                    }
                } else {
                    
//Es binario, se modifico? En ese caso no decimos las diferencias 
                    
if (isset($this->_blobs[$id_fila][$campo]) && $this->_blobs[$id_fila][$campo]['modificado']) {
                        
$diferencias[$campo] = array('anterior' => '?''actual' => '?');
                    }
                }
            }
        }
        return 
$diferencias;
    }



    
/**
     * Verifica si hubo cambios en los valores de un campo especifico
     * @param string $campo Nombre de la columna a comparar
     * @param integer $id_viejos Identificador de la fila con los datos viejos
     * @param array $datos_nuevos Arreglo con los datos nuevos
     * @return boolean
     */
    
function es_campo_modificado($campo$id_viejos$datos_nuevos)
    {
        if (! isset(
$this->_columnas[$campo])) {
            return 
false;
        }
        if (isset(
$this->_datos[$id_viejos][$campo])) {
            
$viejo $this->_datos[$id_viejos][$campo];
            if (
is_bool($viejo)) {
                
$viejo $viejo 0;
            }
            switch (
$this->_columnas[$campo]['tipo']) {
                case 
'N':
                    
//--- Comparacion por igualdad estricta con un cast a Float en caso de Tipo Numero
                    
$modificar = (float)$viejo !== (float)$datos_nuevos[$campo];
                break;
              default:
                    
//--- Comparacion por igualdad estricta con un cast a string
                    
if ($this->persistidor()->get_usar_trim()) {
                        
$modificar = (trim((string) $viejo) !== trim((string) $datos_nuevos[$campo]));
                    } else  {
                        
$modificar = (((string) $viejo) !== ((string) $datos_nuevos[$campo]));
                    }
            }
        } else {
            
//--- Si antes era null, se modifica si ahora no es null! (y si es una columna valida)
            
$modificar = isset($this->_columnas[$campo]) && isset($datos_nuevos[$campo]);
        }
        return 
$modificar;
    }

    
/**
     * Agrega en un nodo xml los datos del registro seleccinado en la tabla por el cursor, como atributos del nodo
     * @param SimpleXMLElement $xml El objeto nodo xml al que se le van a agregar los atributos
     */
    
function get_xml($xml)
    {
        
// Recupera los datos del registro marcado por el cursor
        
$datos $this->get();
        
        
// Para cada columna, la agrega como atributo del nodo
        
foreach($datos as $clave => $valor){
            
$xml->addAttribute($clave,utf8_encode($valor));
        }
    }

}
?>

:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ Read-Only ]

:: Make Dir ::
 
[ Read-Only ]
:: Make File ::
 
[ Read-Only ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 2.1 [PHP 8 Update] [02.02.2022] maintained byC99Shell Github | Generation time: 0.9944 ]--