Introducción
La aplicación “Localidades” se provee como un ejemplo simple, cuyo objetivo es demostrar la forma es que se usan algunas de las características principales de XGAP, además de presentar una posible estructura para una aplicación XGAP.
Su funcionalidad principal permite registrar divisiones geopolíticas: localidades, provincias, países. Además permite asociar múltiples fotos a las localidades.
El modelo de datos está representado en el siguiente diagrama:
Cada una de las entidades “País”, “Provincia”, “Localidad” y “Foto Localidad” cuentan con su correspondiente listado y formulario para consultar y operar con sus instancias, en tanto que la entidad “Continente” tiene sus valores predefinidos en la base de datos y no se asocia a páginas de la aplicación.
El modelo de datos está diseñado para servir como base para demostrar características de XGAP, no para ofrecer una aplicación que tenga una utilidad real. |
Estructura de directorios del proyecto
Parte de la estructura de directorios de una aplicación está predefinida por XGAP; específicamente:
-
Un directorio raíz para los fuentes de la aplicación. XGAP espera que este directorio se encuentre dentro del directorio
APPS_DIR
y tenga el mismo nombre que la aplicación, para que el generador lo detecte, pero también es posible ubicarlo en cualquier otro lugar, con cualquier otro nombre, y crear un enlace enAPPS_DIR
. -
Subdirectorios
extras
,extras/menu
yresultados
dentro de este directorio raíz.
Fuera de esta estructura predefinida, cada proyecto se puede organizar como resulte más conveniente.
Para el caso de esta aplicación, se optó por una estructura autocontenida: tanto la raíz de la aplicación como otros directorios adicionales se encuentran dentro de un único directorio que corresponde al proyecto entero. Este directorio se puede ubicar en cualquier parte del sistema de archivos, independientemente de dónde se encuentre la distribución de XGAP. Para poder generar la aplicación, se debe colocar un enlace a la raíz de la aplicación dentro de APPS_DIR
, con nombre localidades
.
La raíz para XGAP es el subdirectorio desarrollo
, mientras que los demás subdirectorios del mismo nivel contienen archivos que no son usados directamente por XGAP. El subdirectorio desarrollo/extras
contiene subdirectorios adicionales con recursos que va a usar en tiempo de ejecución la aplicación generada.
-
bd
-
Scripts SQL para crear la base de datos inicial completa.
-
bd/cambios
-
Scripts SQL incrementales, para actualizar la base de datos desde una versión anterior.
-
desarrollo
-
Raíz para XGAP, conteniendo los fuentes XML.
-
desarrollo/extras
-
Requerido por XGAP. Fuentes adicionales y otros recursos que se copian directamente a la aplicación generada.
-
desarrollo/extras/clases
-
Clases PHP.
-
desarrollo/extras/img
-
Imágenes.
-
desarrollo/extras/menu
-
Definiciones de menúes.
-
desarrollo/extras/templates
-
Plantillas para usar en reportes.
-
desarrollo/resultados
-
Requerido por XGAP.
-
xsd
-
Contiene los esquemas XML (
*.xsd
) del motor en uso. Este directorio no forma parte de la distribución de la aplicación (no se guarda en el repositorio de código) pero está referenciado por el atributoxsi:noNamespaceSchemaLocation
del elemento raíz de los fuentes XML.Una forma práctica de completar este directorio es crear enlaces simbólicos hacia los archivos
*.xsd
del directorioplantillas
del motor.
En Linux se pueden usar los comandos dados a continuación para crear los enlaces simbólicos necesarios para poder trabajar con la aplicación descargada.
Para este ejemplo, se asume que:
-
La aplicación se encuentra en
/home/user/Projects/xgap/examples/localidades
. -
APPS_DIR
está definido como/home/user/Projects/xgap/apps
. -
El motor usado se encuentra en
/home/user/Projects/xgap/dist/motores/ultimo
. -
{APPS_DIR}/localidades
no existe previamente.
ln -s /home/user/Projects/xgap/examples/localidades/desarrollo \ /home/user/Projects/xgap/apps/localidades for file in /home/user/Projects/xgap/dist/motores/ultimo/plantillas/*.xsd; do ln -s "$file" /home/user/Projects/xgap/examples/localidades/xsd/ done
Esquema de la base de datos
El esquema de base de datos que usa la aplicación (creado por los scripts que se encuentran en el subdirectorio bd
) está representado en la figura
[Diagrama generado con SchemaCrawler]
siguiente.
Además de las tablas y vistas predefinidas por XGAP, el esquema contiene las correspondientes al modelo de datos y algunas auxiliares:
-
Modelo
-
Tabla
public.continente
-
Tabla
public.pais
-
Tabla
public.provincia
-
Tabla
public.localidad
-
Tabla
public.fotolocalidad
-
Vista
public.vprovincia
-
Vista
public.vlocalidad
-
Vista
public.vfotolocalidad
-
-
Auxiliares
-
Tabla
sistema.traduccion
-
Tabla
sistema.traduccionboolean
-
Tabla
sistema.propiedad
-
Componentes
A continuación se listan los principales fuentes XML, y las tablas y vistas más relevantes que usa cada uno de ellos.
Fuente | Usa | Diagrama |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
El diagrama siguiente presenta las páginas (archivos generados a partir de los fuentes XML) principales que componen la aplicación y los caminos que se pueden seguir para navegar a cada una de ellas. Para simplificar el diagrama, no se incluyen las versiones de los listados en formatos no HTML (configuracion/imprimibles/imprimir
).
Contenido común a todas las páginas
Las opciones de configuración de la aplicación norte
, sur
, este
y oeste
permiten especificar archivos PHP que se incluyen en todas las páginas, dentro de la sección de la página que indica el nombre de cada opción. En el caso de esta aplicación, sólo se usan las secciones norte
y sur
. La configuración, en conf.inc.php
, es:
<?php define('XGAP_CONF_NORTE', 'norte.php'); define('XGAP_CONF_SUR', 'sur.php'); define('XGAP_CONF_ESTE', 'ignorar'); define('XGAP_CONF_OESTE', 'ignorar');
Los archivos norte.php
y sur.php
se encuentran dentro del directorio desarrollo/extras
. La salida que producen se emite en la parte superior e inferior, respectivamente, de todas las páginas.
norte
y sur
en una página de la aplicación “Localidades”En norte.php
se definen tres áreas:
-
La cabecera, que contiene el título de la aplicación envuelto en un enlace que lleva a la página de inicio.
<?php Html::link(Configuracion::paginaInicio() . '?' . PARAMETRO_HISTORIAL . '=reset', XGAP_APP_TITULO, null, null, false, null, null, 'Inicio');
-
<?php Html::abrirTag('div', 'menu-ppal', null, null, false, null, false, null, false); // Html::cerrarTag('div'); // ... $menu_params = array(PARAMETRO_HISTORIAL => 'skip'); $param_recarga_menu = Request::obtener(RequestXgap::PARAMETRO_RECARGA_MENU, null); if (param2boolean($param_recarga_menu)) { $menu_params[RequestXgap::PARAMETRO_RECARGA_MENU] = $param_recarga_menu; } $menu = crearDireccionPagina('ppal_menu.php', $menu_params, false); // $gfxpath = XGAP_CONF_PREFIJO_WEB . '/recursos/imagenes/menu/'; Html::abrirJavascript(); echo <<<MENU if (typeof dhtmlXMenuBarObject != 'undefined') { aMenuBar = new dhtmlXMenuBarObject( document.getElementById("menu-ppal"), // "100%", 23, "" ); aMenuBar.setGfxPath("$gfxpath"); aMenuBar.loadXML("$menu"); aMenuBar.showBar(); } MENU; Html::cerrarJavascript(); unset($param_recarga_menu, $menu_params, $menu, $gfxpath);
Emite el elemento vacío dentro del cual se va a construir el menú. El archivo ppal_menu.php
produce la estructura del menú. Ver sección Menú de la aplicación.Se crea el menú dentro del elemento creado para ello. -
Una barra de sesión, con la ruta de navegación (breadcrumb), el nombre del usuario logueado y el link para cerrar la sesión.
<?php echo Componentes::barraHistorial( $GLOBALS['objeto_historial'], 'breadcrumb', ' » ', XGAP_CONF_ITEMS_BARRA_HISTORIAL ); // ... if (Contexto::existe('usuario_sistema')) { // ... Html::elemento( 'span', Contexto::obtener('usuario_sistema'), null, 'sesion-usuario', null ); Html::elemento( 'span', '[ ' . Html::link( 'logout_contenido.php?' . PARAMETRO_HISTORIAL . '=reset', 'Salir', null, null, true) . ' ]', null, 'sesion-cerrar', null ); // ... }
En sur.php
se muestra:
-
El nombre y rol del usuario logueado
<?php $usuario = Contexto::existe('nombre_usuario') // ? Contexto::obtener('nombre_usuario') : Contexto::obtener('usuario_sistema', null); // if (!empty($usuario)) { Html::elemento( 'span', Formateo::prepararSalidaString($usuario, null, null, true, true), null, 'sesion-usuario', null ); if (Contexto::existe('rol')) { // Html::elemento( 'span', ' (' . Formateo::prepararSalidaString( Contexto::obtener('rol'), null, null, true, true) . ')', null, 'sesion-rol', null ); } echo ' – '; }
Las variables 'nombre_usuario'
,'usuario_sistema'
y'rol'
se almacenan en la sesión después del login y selección de rol. -
El nombre y versión de la aplicación
<?php Html::elemento('span', XGAP_APP_TITULO, // null, 'app-titulo', null); Html::elemento('span', ' v. ' . XGAP_CONF_VERSION_APLICACION, // null, 'app-version', null);
XGAP_APP_TITULO
yXGAP_CONF_VERSION_APLICACION
son dos constantes predefinidas por XGAP.
Menú de la aplicación
El menú de la aplicación está definido en el archivo desarrollo/extras/menu/ppal_menu.xml
. Este archivo se procesa durante la generación de la aplicación, produciendo el archivo ppal_menu.php
, que se carga en norte.php
(ver sección Contenido común a todas las páginas) para construir el menú cuando se cargan las páginas.
<menu xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../xsd/menu.xsd" aplicacion="localidades" cache="rol"> <carpeta nombre="Sistema"> <carpeta nombre="Seguridad" roles="ADMINISTRADOR"> <item nombre="Usuarios" url="usuario_listado.php?xgap_historial=reset"/> <item nombre="Roles funcionales" url="rolf_listado.php?xgap_historial=reset"/> <item nombre="Roles por Usuario" url="rol_compu_usu_listado.php?xgap_historial=reset"/> <separador/> <item nombre="Establecer permisos de página por rol" url="seguridad_contenido.php?xgap_historial=reset"/> <item nombre="Permisos por página" url="paginaapp_listado.php?xgap_historial=reset"/> </carpeta> <separador roles="ADMINISTRADOR"/> <item nombre="Cambio de Rol" url="seleccionarrol_contenido.php?xgap_historial=reset"/> <item nombre="Cambio de Contraseña" url="usuario_cambioclave_formulario.php?xgap_historial=reset&agregado=false"/> <separador/> <item nombre="Acerca de..." url="acercade_contenido.php?xgap_historial=reset"/> <separador/> <item nombre="Salir" url="logout_contenido.php?xgap_historial=reset"/> </carpeta> <carpeta nombre="Geografía"> <item nombre="Países" url="pais_listado.php?xgap_historial=reset"/> <item nombre="Provincias" url="provincia_listado.php?xgap_historial=reset"/> <item nombre="Localidades" url="localidad_listado.php?xgap_historial=reset"/> </carpeta> </menu>
El valor "rol" en el atributo cache (o la ausencia del atributo, dado que "rol" es su valor por defecto) hace que la estructura que define el menú (la salida emitida por ppal_menu.php ) se guarde en la sesión del usuario, asociada al rol actual. Si el rol cambia, la estructura se reconstruye y se vuelve a guardar. Si se usara cache="no" , la estructura del menú se reconstruiría en cada solicitud a ppal_menu.php . Si se usara cache="usuario" , la estructura se guardaría en el cache asociada al usuario, no al rol, y se seguiría retornando la misma aún ante un cambio de rol por parte del usuario; como en este menú se usa el atributo roles en algunos de los elementos (explicado a continuación), este comportamiento sería incorrecto, porque evitaría que se determinen los elementos a mostrar para el nuevo rol seleccionado.
| |
El atributo roles , disponible en los elementos carpeta , item y separador , permite indicar una lista de roles para los cuales se debe mostrar el elemento. En este caso, la carpeta “Sistema” → “Seguridad” y el separador adyacente sólo se muestran para el rol “ADMINISTRADOR”. Los elementos que no tienen el atributo se muestran para todos los roles.
|
En esta aplicación se utiliza un único menú para todos los usuarios y roles, pero también es posible definir más de un menú y cargar uno u otro de acuerdo a algún criterio relevante para la aplicación. Por ejemplo, se podría definir un menú diferente para cada rol y cargar el que corresponda al rol activo. El ejemplo siguiente muestra una posible forma de implementar este caso.
-
Especificar un archivo de definición separado para cada rol.
-
En el código donde se carga el menú, seleccionar el archivo php que corresponda al rol activo.
En
norte.inc.php
:<?php // ... $archivo_menu = 'rol_' . Contexto::obtener('rol') . '_menu.php'; // if (!file_exists($archivo_menu)) { $archivo_menu = 'ppal_menu.php'; // } $menu_params = array(PARAMETRO_HISTORIAL => 'skip'); $param_recarga_menu = Request::obtener(RequestXgap::PARAMETRO_RECARGA_MENU, null); if (param2boolean($param_recarga_menu)) { $menu_params[RequestXgap::PARAMETRO_RECARGA_MENU] = $param_recarga_menu; } $menu = crearDireccionPagina($archivo_menu, $menu_params, false); // // ...
Se construye el nombre del archivo en base al rol del usuario. Si el archivo no existe (el rol actual no tiene un menú específico) se usa un menú genérico. Se construye la dirección de la página a cargar, usando el archivo seleccionado.
Páginas y documentos
En esta sección se describen las páginas y documentos más relevantes de la aplicación, destacando algunas de las características de XGAP que utilizan, el código relacionado y cómo se refleja en la interfaz a usuario, si corresponde.
Tipo | Fuentes |
---|---|
Contenido |
acercade_contenido.xml, error_contenido.xml, error_formulario_contenido.xml, fotolocalidad_contenido.xml, guardarindicepaginas_contenido.xml, index_admin_contenido.xml, index_contenido.xml, login_contenido.xml, seguridad_contenido.xml, seleccionarrol_contenido.xml |
Formulario |
fotolocalidad_formulario.xml, localidad_formulario.xml, paginaapp_formulario.xml, pais_formulario.xml, provincia_formulario.xml, rol_compu_usu_formulario.xml, rolf_formulario.xml, usuario_cambioclave_formulario.xml, usuario_cambioclaveadmin_formulario.xml, usuario_formulario.xml, usuario_sinclave_formulario.xml |
Listado |
fotolocalidad_listado.xml, localidad_listado.xml, paginaapp_listado.xml, pais_listado.xml, permiso_pagina_export_listado.xml, provincia_listado.xml, rol_compu_usu_listado.xml, rolf_listado.xml, usuario_listado.xml |
Master |
|
Reporte ODT |
Característica | Usada en |
---|---|
Código extra |
acercade_contenido.xml ( |
Comando “Volver” con destino variable |
|
Comando “Volver” condicional |
error_contenido.xml, error_formulario_contenido.xml |
Contenido de página personalizado |
acercade_contenido.xml, index_admin_contenido.xml, error_contenido.xml, error_formulario_contenido.xml, fotolocalidad_contenido.xml, guardarindicepaginas_contenido.xml, login_contenido.xml |
Contenido extra en la cabecera HTML |
acercade_contenido.xml, index_admin_contenido.xml, login_contenido.xml, fotolocalidad_contenido.xml |
Especificación de variedades de página a generar |
fotolocalidad_listado.xml, localidad_listado.xml, paginaapp_listado.xml, pais_listado.xml, permiso_pagina_export_listado.xml, provincia_listado.xml, rolf_listado.xml |
Formato personalizado en columna de listado |
|
Formulario con campo de tipo |
pais_formulario.xml, usuario_formulario.xml, usuario_sinclave_formulario.xml |
Formulario con campo de tipo |
|
Formulario con operación personalizada |
paginaapp_formulario.xml ( |
Listado con acciones que operan sobre filas seleccionadas |
|
Listado con buscador personalizado |
localidad_listado.xml, paginaapp_listado.xml, provincia_listado.xml |
Listado con columnas condicionales |
|
Listado con columnas de imágenes |
|
Listado con columnas de acciones |
|
Mensajes de página |
|
Página con acciones generales |
|
Uso de XInclude |
usuario_cambioclave_formulario.xml, usuario_cambioclaveadmin_formulario.xml, usuario_sinclave_formulario.xml |
acercade_contenido.xml
Página que muestra información acerca de la aplicación.
- Contenido extra en la cabecera HTML
-
Se agregan reglas CSS adicionales que definen estilos específicos para esta página.
<pagina> .... <head-extra> <![CDATA[ <style type="text/css"> .... </style> ]]> </head-extra>
Una desventaja de incluir el código CSS en el fuente XML es que los estilos especificados permanecen fijos por más que se cambie el skin en uso.
En lugar de hacerlo de esta manera, se puede cargar CSS adicional incluyendo uno o más archivos externos, a través del elemento
configuracion/inclusiones/archivo
. Si la ruta incluye la constanteXGAP_CONF_SKIN_DIR
, el archivo cargado va a depender del skin en uso. Por ejemplo:<configuracion> <inclusiones> <archivo tipo="css"> <path>XGAP_CONF_SKIN_DIR . 'extras.css'</path> </archivo> ...
- Contenido de la página
-
Hace uso de varias constantes predefinidas por XGAP, que contienen información de la aplicación, el motor y el entorno.
<?php //... Html::elemento('h1', XGAP_APP_TITULO, 'acercade-app'); Html::elemento('p', 'Versión: ' . XGAP_CONF_VERSION_APLICACION, 'acercade-version'); Html::elemento('p', 'Aplicación de ejemplo de XGAP.', 'acercade-texto'); if (!XGAP_CONF_EN_PRODUCCION) { Html::elemento('p', 'Generada con XGAP ' . XGAP_VERSION_MOTOR_GEN . ' en ' . date('c', XGAP_TIMESTAMP_GEN), 'info-generacion'); Html::elemento('p', 'Corriendo sobre XGAP ' . XGAP_VERSION_MOTOR, 'info-ejecucion'); }
-
Código extra PHP en
despues_inicializacion
-
Incluye el título de la aplicación en el título de la página.
<?php $params['titulo'] = 'Acerca de ' . XGAP_APP_TITULO;
error_contenido.xml
- Comando “Volver” condicional
-
El comando sólo se muestra cuando la página no es simple.
<pagina> <configuracion> <volver> <!-- ... --> <condicion>return !defined('XGAP_PAGINA_SIMPLE') || !XGAP_PAGINA_SIMPLE;</condicion>
- Comando “Volver” con destino variable
-
El destino del comando “Volver” en esta página varía de acuerdo al estado del historial. Se usa
volver/codigo-destino
para seleccionar la página de retorno correcta.<pagina> <configuracion> <volver> <codigo-destino> <![CDATA[ global $objeto_historial; if (!$objeto_historial->vacio() && strpos( $objeto_historial->actual()->uri(), basename($_SERVER['REQUEST_URI']) ) !== false) { $objeto_historial->remover(); } if (Request::existe('origen') && !$objeto_historial->vacio() && strpos( $objeto_historial->actual()->uri(), Request::obtener('origen') ) !== false) { $objeto_historial->remover(); } $retorno = !$objeto_historial->vacio() ? $objeto_historial->actual()->uri() : Configuracion::paginaInicio(); $retorno = 'location="' . htmlspecialchars($retorno) . '"'; return $retorno; ]]> </codigo-destino>
error_formulario_contenido.xml
- Comando “Volver” condicional
-
Ver error_contenido.xml.
- Comando “Volver” con destino variable
-
El destino del comando “Volver” en esta página varía de acuerdo al estado del historial. Se usa
volver/codigo-destino
para seleccionar la página de retorno correcta.En
error_formulario_contenido.xml
:<pagina> <configuracion> <volver> <codigo-destino> <![CDATA[ global $retorno; return 'location="' . htmlspecialchars($retorno) . '"'; ]]> </codigo-destino>
En
error_formulario_contenido-anterior.inc.php
:<?php //... $cur = basename($_SERVER['REQUEST_URI']); if (!$objeto_historial->vacio() && strpos($objeto_historial->actual()->uri(), $cur) !== false) $objeto_historial->remover(); if (!$objeto_historial->vacio() && strpos($objeto_historial->actual()->uri(), $cur) !== false) $objeto_historial->remover(); $retorno = !$objeto_historial->vacio() ? $objeto_historial->primero(array($cur))->uri() : Configuracion::paginaInicio(); //...
fotolocalidad_contenido.xml
Esta página está construida principalmente con código PHP. El código en fotolocalidad_contenido-anterior.inc.php
valida los parámetros de request y obtiene los datos necesarios desde la base de datos. El código en fotolocalidad_contenido.inc.php
produce el HTML del cuerpo de la página.
fotolocalidad_formulario.xml
Formulario de alta/baja/modificación de foto de localidad.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="fotolocalidad"/> ... <campos> <campo tipo="Oculto"> <dato>nlocalidad</dato> </campo> <campo tipo="Archivo"> <dato>varchivo</dato> <titulo>Archivo</titulo> <tip>Archivo de imagen</tip> </campo> <campo ancho="80"> <dato>vubicacion</dato> <titulo>Ubicación</titulo> <tip>Lugar o dirección donde se tomó la foto.</tip> <texto-autocompletable comparacion="cont" demora="500"/> </campo> <campo tipo="HtmlArea" columnas="600" filas="300"> <dato>tdescripcion</dato> <titulo>Descripción</titulo> </campo> </campos>
fotolocalidad.varchivo character varying(2048) NOT NULL
. El nombre y destino del archivo subido se construye en código extra.fotolocalidad.vubicacion character varying(100) NOT NULL
.fotolocalidad.tdescripcion text
. -
Código extra PHP en
antes_procesar_uploads
-
Modifica la ruta y nombre del archivo subido:
-
Los archivos se guardan separados por localidad. La ruta tiene la forma
{XGAP_CONF_UPLOAD_DIR}/localidad/fotos/{nlocalidad}/
. -
El nombre de cada archivo se prefija con la fecha y hora de alta, y el identificador del usuario que la realiza.
<?php $campo_varchivo = $params['campos']['varchivo']; if (!empty($campo_varchivo['upload'])) { $upload = $campo_varchivo['upload']; $upload->cambiarDirDestino( ruta_archivo(array('localidad', 'fotos', $params['registro']['nlocalidad'])), true ); $upload->cambiarNombreArchivo( date('YmdHis') . '-' . Contexto::obtener('ident_usuario') .'-' . $upload->nombreArchivo() ); }
-
fotolocalidad_listado.xml
Listado de fotos de localidades.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<columnas> <checkbox> <tip>Seleccione las fotos a imprimir</tip> <nombre>fotosimp</nombre> <controlSeleccionTodos/> </checkbox> <columna intervieneEnbusqueda="no" permiteOrdenar="false"> <titulo>Foto</titulo> <dato>varchivo</dato> <imagen> <miniatura/> <link pasarParametros="true" historial="skip"> <url>fotolocalidad_contenido.php</url> <target inline="true"/> </link> </imagen> </columna> <columna llevaAForm="true"> <titulo>Ubicación</titulo> <tip>Lugar o dirección donde se tomó la foto</tip> <dato>vubicacion</dato> </columna> <columna> <titulo>Descripción</titulo> <dato>tdescripcion</dato> </columna> </columnas>
Columna de checkboxes, usada para seleccionar las filas a incluir en la acción "Imprimir seleccionadas". fotolocalidad.varchivo character varying(2048)
. Columna de imágenes, que muestra miniaturas de las fotos y permite abrirfotolocalidad_contenido.php
.fotolocalidad.vubicacion character varying(100)
.fotolocalidad.tdescripcion text
. - Especificación de variedades a generar
-
Sólo se genera el listado normal, dado que las demás variedades no se usan.
<generacion> <salidas> <normal/> </salidas> </generacion>
- Acción sobre filas seleccionadas
-
El listado provee la acción “Imprimir seleccionadas”, que genera el reporte ODT
fotolocalidad_reporte_odt
con las filas seleccionadas por el usuario a través de los checkboxes en la columnafotosimp
.<configuracion> <!-- ... --> <acciones> <accion nombre="imprimir" clase="ButtonImprimir"> <descripcion>Imprimir seleccionadas</descripcion> <destino historial="skip"> <url>fotolocalidad_reporte_odt.php</url> <parametros> <columna> <nombre>fotosimp</nombre> </columna> <parametro nombre="ancho" valor="90"/> </parametros> </destino> <reset> <columna>fotosimp</columna> </reset> </accion> </acciones> </configuracion>
La acción realiza un request a fotolocalidad_reporte_odt.php
.Se pasa como parámetro la columna de tipo checkbox
fotosimp
. El resultado es que el request lleva un parámetro con nombrefotosimp
que tiene como valor la lista de filas seleccionadas, dada como una secuencia de los valores correspondientes denfotolocalidad
(/listado/clave/campo
) separados por comas.Después de ejecutar la acción se reinicia el estado de selección los checkboxes. -
Código extra PHP en
despues_inicializacion
-
Agrega el nombre de la ciudad al título de la página.
<?php if (Request::existe('nlocalidad')) { $nlocalidad = intval(Request::obtener('nlocalidad'), 10); $sql = "SELECT vnombrecompleto FROM vlocalidad WHERE nlocalidad = $nlocalidad"; $localidad = $params['conexion']->obtenerPrimero($sql); if (!empty($localidad)) { $params['titulo'] .= ' – ' . preparar_salida($localidad); } }
fotolocalidad_reporte_odt.xml
Reporte ODT invocado desde la acción “Imprimir seleccionadas” en fotolocalidad_listado_xml
.
<configuracion> <template path="templates/fotolocalidad.odt"> <xslt-adicional incluir-templates-predefinidos="true"/> </template> <xml nombre="fotos_localidad"/> <reporte nombre="fotos_localidad" incluir-fechahora-emision="true"> <meta> <item nombre="dc:title"><![CDATA[Fotografías de {$var_nombrecompletolocalidad}]]></item> <item nombre="dc:language">es</item> <item nombre="meta:editing-cycles">1</item> <item nombre="meta:editing-duration">P0D</item> <item nombre="meta:creation-date"><![CDATA[{{ date('Y-m-d\TH:i:s') }}]]></item> <item nombre="meta:initial-creator"><![CDATA[{{ Contexto::obtener('nombre_usuario') }}]]></item> </meta> </reporte> </configuracion> <variables> <variable nombre="nlocalidad" tipo="request"> <requerida permitir-vacio="false" cero-es-vacio="true"/> </variable> <variable nombre="fotosimp_nfotolocalidad" tipo="request"/> </variables> <php> <![CDATA[ function sanitize_db_serial($valor) { return filter_var( $valor, FILTER_VALIDATE_INT, array('options' => array('default' => 0, 'min_range' => 1)) ); } $var_nlocalidad = sanitize_db_serial($var_nlocalidad); $html_entity_decode_flags = ENT_QUOTES; if (defined('ENT_XHTML')) { // PHP >= 5.4.0 $html_entity_decode_flags |= constant('ENT_XHTML'); } ]]> </php> <consultas> <consulta> <sql> <![CDATA[ SELECT vnombrecompleto, tdescripcion FROM vlocalidad WHERE nlocalidad = ? ]]> </sql> <parametros> <parametro nombre="nlocalidad"/> </parametros> <variables> <variable columna="vnombrecompleto" nombre="nombrecompletolocalidad"/> <variable columna="tdescripcion" nombre="descripcionlocalidad"/> </variables> </consulta> </consultas> <php> <![CDATA[ $var_fotosimp_nfotolocalidad = implode( ',', array_filter( array_map( 'sanitize_db_serial', explode( ',', $var_fotosimp_nfotolocalidad ) ) ) ); if (!empty2($var_fotosimp_nfotolocalidad, false, true)) { ]]> </php> <iterator nombre="fotos"> <consulta> <sql> <![CDATA[ SELECT varchivo, vubicacion, tdescripcion FROM vfotolocalidad WHERE nfotolocalidad IN (?) ORDER BY vubicacion, nfotolocalidad DESC ]]> </sql> <parametros> <parametro nombre="fotosimp_nfotolocalidad"/> </parametros> <variables> <variable columna="varchivo" nombre="archivo"/> <variable columna="vubicacion" nombre="ubicacion"/> <variable columna="tdescripcion" nombre="descripcion"/> </variables> </consulta> <contenido> <php> <![CDATA[ $var_descripcion = html_entity_decode( strip_tags($var_descripcion), $html_entity_decode_flags ); $var_archivo = realpath( ruta_archivo(array(Upload::dirBase(), $var_archivo), DIR_SEP, false) ); if (!$var_archivo) { $var_archivo = ''; } ]]> </php> <xml nombre="foto"> <![CDATA[ <foto> <ubicacion><![CDATA[{$var_ubicacion}]]></ubicacion> <descripcion><![CDATA[{$var_descripcion}]]></descripcion> <archivo><![CDATA[{$var_archivo}]]></archivo> </foto> ]]> </xml> </contenido> </iterator> <php> <![CDATA[ } else { // empty2($var_fotosimp_nfotolocalidad, false, true) $odt_xml_node_fotos = ''; } ]]> </php> <!-- Estructura generada: <localidad> 1 <nombre></nombre> 1 <descripcion></descripcion> * <foto> <ubicacion></ubicacion> <descripcion></descripcion> <archivo></archivo> </foto> </localidad> --> <xml nombre="fotos_localidad" main="si"> <![CDATA[ <localidad> <nombre><![CDATA[{$var_nombrecompletolocalidad}]]></nombre> <descripcion><![CDATA[{$var_descripcionlocalidad}]]></descripcion> {$odt_xml_node_fotos} </localidad> ]]> </xml>
La plantilla del reporte es el archivo templates/fotolocalidad.odt .
| |
Se incluyen los templates xslt predefinidos, para usarlos en fotolocalidad.odt , como se muestra en la descripción de la plantilla.
| |
Se personalizan los metadatos que va a contener el ODT generado. | |
Se definen variables a partir de los parámetros de request, para usar más adelante. | |
Saneado de una variable con valor proveniente del request. La otra variable definida hasta ese punto se procesa más adelante. | |
Más adelante será necesario usar html_entity_decode() , dentro de un iterador. Aquí se define el valor de su segundo parámetro.
| |
Consulta para obtener los datos necesarios acerca de la localidad solicitada, cuya clave está dada por la variable nlocalidad que se definió a partir del request. Con el resultado de esta consulta se definen las variables nombrecompletolocalidad y descripcionlocalidad .
| |
Saneado de una variable con valor proveniente del request. En este caso se asegura que el valor se pueda utilizar en una cláusula SQL IN sin riesgo de inyección de código.
| |
Sólo se debe hacer la consulta para obtener los datos de las fotos si la especificación de sus claves no es vacía. | |
Iterador para construir los datos de las fotos solicitadas. | |
Consulta para obtener los datos de las fotos solicitadas, cuyas claves se recibieron en el request y se sanearon en ➑. Para cada fila se asignan valores a las variables archivo , ubicacion y descripcion , que se usan a continuación.
| |
La descripción de la foto tiene formato HTML con entidades codificadas. Aquí se eliminan los elementos HTML y se decodifican las entidades, para que se pueda incluir sin problemas en el ODT. | |
Se construye la ruta completa al archivo de imagen. | |
Para cada fila retornada por la consulta, se construye un fragmento de XML con los valores de esa fila. El fragmento XML final para el iterador resulta de la concatenación de los fragmentos de cada iteración. | |
else del if en ➒: si no hay claves de fotos a obtener, el fragmento de XML correspondiente queda vacío.
| |
El elemento xml principal define el XML final que se procesa con la plantilla dada en ➊. Usa las variables definidas en ➐ y el fragmento de XML creado por el iterador ➓ (o el fragmento vacío en ⓯).
|
El XML generado por el código descripto, que produce el reporte mostrado al comienzo de esta sección, es el siguiente:
<localidad> <nombre><![CDATA[TANDIL, BUENOS AIRES, ARGENTINA]]></nombre> <descripcion><![CDATA[Tandil es la ciudad cabecera del partido homónimo y está ubicada en el centro de la provincia de Buenos Aires, en el centro-este de la Argentina, sobre las sierras del sistema de Tandilia. Fue fundada {...} Su clima es templado, con temperaturas medias de 13,7° C. (Fuente: Wikipedia Español)]]></descripcion> <foto> <ubicacion><![CDATA[PARQUE INDEPENDENCIA]]></ubicacion> <descripcion><![CDATA[Monumento al general Martín Rodríguez, fundador de Tandil, en el Parque Independencia. Hecho en bronce por el artista Arturo Dresco. By Arturo Dresco (Own work) [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons]]></descripcion> <archivo><![CDATA[{ruta completa a XGAP_CONF_UPLOAD_DIR}/localidad/fotos/1/20151113125222-1-MonumentoGralRodriguez.jpg]]></archivo> </foto> <foto> <ubicacion><![CDATA[TANDIL DESDE LA MOVEDIZA]]></ubicacion> <descripcion><![CDATA[La ciudad rodeada de las sierras. {...}]]></descripcion> <archivo><![CDATA[{ruta completa a XGAP_CONF_UPLOAD_DIR}/localidad/fotos/1/20151113124722-1-Tandil_desde_La_Movediza_2.JPG]]></archivo> </foto> <foto> <ubicacion><![CDATA[VILLA DEL LAGO]]></ubicacion> <descripcion><![CDATA[Monumento de Don Quijote y Sancho Panza, {...}]]></descripcion> <archivo><![CDATA[{ruta completa a XGAP_CONF_UPLOAD_DIR}/localidad/fotos/1/20151113123118-2-tandil-monumento_don_quijote-28-12-08_1834.jpg]]></archivo> </foto> </localidad>
La plantilla templates/fotolocalidad.odt
contiene los elementos que se describen a continuación, los cuales construyen el ODT final a partir del XML generado.
➀ y ➁ son campos placeholder de tipo text (Insert → Fields → More Fields; Pestaña Functions, Type=Placeholder, Format=Text). ➂, ➃ y ➄ son scripts con tipo ODF-XSLT
(Insert → Script; Script type=ODF-XSLT
).
➀
|
Campo placeholder con valores Placeholder= |
||||||
➁
|
Campo placeholder con valores Placeholder= |
||||||
➂
|
Script para incluir la descripción de la localidad: {@before ancestor::text:p[1] <xslt:if test="normalize-space(/localidad/descripcion) != ''">} {@after ancestor::text:p[1] </xslt:if>} {@replace . <xslt:call-template name="eol-to-line-break"> <xslt:with-param name="src" select='/localidad/descripcion'/> </xslt:call-template>}
|
||||||
➃
|
Script para incluir la descripción de la foto: {@replace . <xslt:choose> <xslt:when test="normalize-space(descripcion) != ''"> <xslt:call-template name="eol-to-line-break"> <xslt:with-param name="src" select='descripcion'/> </xslt:call-template> </xslt:when> <xslt:otherwise>(sin descripción)</xslt:otherwise> </xslt:choose>}
|
||||||
➄
|
Script para recorrer las fotos incluidas y referenciar el archivo de imagen de cada una: {@before //table:table[@table:name="TablaFoto"] <xslt:for-each select="/localidad/foto">} {@after //table:table[@table:name="TablaFoto"] </xslt:for-each>} {@child //draw:frame[@draw:name="foto"]/draw:image <xslt:if test="normalize-space(archivo) != ''"> <xslt:attribute name="xlink:href"> <xslt:value-of select="archivo"/> </xslt:attribute> </xslt:if> } |
||||||
➅
|
El contenido del pie de página no depende de los valores recibidos en el XML de entrada; sólo se incluyeron campos comunes, no procesados por la transformación XSLT. |
Notar que en ➂ y ➃ se hace un llamado al template XSLT eol-to-line-break
, para preservar en el documento generado los saltos de línea que pudiera haber en el texto origen (/localidad/descripcion
y /localidad/foto/descripcion
, respectivamente); si no se hiciera así, los saltos de línea serían ignorados. Dicho template está disponible porque en fotolocalidad_reporte_odt.xml
se indica que se deben incluir los templates predefinidos por XGAP (/pagina/configuracion/template/xslt-adicional/@incluir-templates-predefinidos="true"
).
guardarindicepaginas_contenido.xml
Actualiza el índice de páginas en la base de datos (tabla seguridad.pagina
).
La implementación de la funcionalidad de la página se encuentra en el archivo extras/guardarindicepaginas_contenido-anterior.inc.php
; el código que genera la salida HTML está en extras/guardarindicepaginas_contenido.inc.php
.
- Uso de mensajes de página
-
XGAP provee un mecanismo para registrar mensajes que se deben mostrar al usuario en un request particular a una página. Esta página lo utiliza para informar cualquier problema que pudiera producirse durante la ejecución de su funcionalidad principal.
En
guardarindicepaginas_contenido-anterior.inc.php
:<?php //... if (isset($indice_paginas)) { //... if ($rs) { //... } else { Pagina::instancia()->mensajes()->agregar(ObjetoMensaje::nuevo( 'No se pudieron limpiar las páginas sin permisos.', Mensaje::TIPO_ALERTA) ); } //... if ($n_errores > 0) { Pagina::instancia()->mensajes()->agregar(ObjetoMensaje::nuevo( "Hubo errores en la actualización de $n_errores páginas.", Mensaje::TIPO_ALERTA) ); } //... } else { Pagina::instancia()->mensajes()->agregar(ObjetoMensaje::nuevo( 'No se encuentra el índice de páginas.', Mensaje::TIPO_ERROR) ); }
En
guardarindicepaginas_contenido.inc.php
:<?php //... if (!Pagina::instancia()->mensajes()->isEmpty()) { Mensaje::mostrarLista( Pagina::instancia()->mensajes(), true, null, 'cont-msjs-pag' ); } //...
El último fragmento de código se encarga de mostrar la lista de mensajes que fueron agregados en el request actual. Este código ya se encuentra incluido en los otros tipos de página, pero en las de tipo Contenido se debe agregar manualmente en el lugar deseado.
- Presentación de resultados
-
El único contenido que se emite en el cuerpo de esta página es un reporte de resultados.
<?php if ($agregadas > 0 || $actualizadas > 0 || $eliminadas > 0) { Mensaje::info( Html::elemento( 'p', Html::elemento( 'strong', 'Actualización terminada.', null, null, null, true ), null, 'first', null, true ) . Html::lista( array( $eliminadas == 0 ? 'No se eliminaron páginas.' : "Se eliminaron $eliminadas páginas.", $agregadas == 0 ? 'No se agregaron páginas.' : "Se agregaron $agregadas páginas.", $actualizadas == 0 ? 'No se actualizaron páginas.' : "Se actualizaron $actualizadas páginas." ), false, null, 'last', null, null, true ), null, 'icon' ); } else { Html::elemento( 'p', 'El índice ya estaba actualizado. No se realizaron cambios.' ); }
index_admin_contenido.xml
Página de inicio para el rol ADMINISTRADOR. Presenta un resumen de la cantidad de elementos que hay en las entidades principales del modelo de datos.
El código PHP de los elementos <contenido-anterior> <![CDATA[ include 'index_admin_contenido-anterior.inc.php'; ]]> </contenido-anterior> <contenido> <![CDATA[ <?php include 'index_admin_contenido-contenido.inc.php'; ?> ]]> </contenido> Los archivos |
- Contenido extra en la cabecera HTML
- Contenido de la página
-
En
index_admin_contenido-anterior.inc.php
:<?php //... $n_paises = $objeto_conexion->obtenerPrimero( 'SELECT COUNT(npais) FROM pais' ); $n_provincias = $objeto_conexion->obtenerPrimero( 'SELECT COUNT(nprovincia) FROM provincia' ); $n_localidades = $objeto_conexion->obtenerPrimero( 'SELECT COUNT(nlocalidad) FROM localidad' ); $n_fotos = $objeto_conexion->obtenerPrimero( 'SELECT COUNT(nfotolocalidad) FROM fotolocalidad' );
En
index_admin_contenido-contenido.inc.php
:<?php //... $info = ''; $resumen = array(); if (!is_null($n_paises)) { $resumen[] = array('Países', $n_paises); } if (!is_null($n_provincias)) { $resumen[] = array('Provincias', $n_provincias); } if (!is_null($n_localidades)) { $resumen[] = array('Localidades', $n_localidades); } if (!is_null($n_fotos)) { $resumen[] = array('Fotos', $n_fotos); } if (!empty($resumen)) { foreach ($resumen as $n => $v) { $resumen[$n] = Html::elemento ( 'em', preparar_salida("{$v[0]}:", array(), null, true, true), null, null, null, true ) . ' ' . $v[1]; } $info .= Html::abrirDiv('resumen', null, null, true) . Html::elemento('h2', 'Resumen', null, null, null, true) . Html::lista($resumen, false, null, null, null, null, true) . Html::cerrarDiv(true); } print $info; //...
localidad_formulario.xml
Formulario de alta/baja/modificación de localidad.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="localidad"/> ... <campos> <campo tipo="Seleccionable" ancho="60"> <titulo>Provincia</titulo> <dato>nprovincia</dato> <entidad>provincia</entidad> <tabla>vprovincia</tabla> <descripcion>vnombrecompleto</descripcion> <seleccionar>nprovincia</seleccionar> </campo> <campo ancho="60"> <titulo>Nombre</titulo> <dato>vnombre</dato> </campo> <campo> <titulo>Prefijo telefónico</titulo> <dato>cpreftelef</dato> </campo> <campo tipo="TextAreaSinTamanio" filas="8" columnas="60" mayusculas="false"> <titulo>Descripción</titulo> <dato>tdescripcion</dato> </campo> </campos>
localidad.nprovincia integer NOT NULL
. El Seleccionable abreprovincia_listado_seleccion.php
.localidad.vnombre character varying(100) NOT NULL
.localidad.cpreftelef character(5)
.localidad.tdescripcion text
.
localidad_listado.xml
Listado de Localidades.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<columnas> <columna> <titulo>País</titulo> <dato>vnombrepais</dato> </columna> <columna> <titulo>Provincia</titulo> <dato>vnombreprovincia</dato> </columna> <columna llevaAForm="true"> <titulo>Nombre</titulo> <dato>vnombre</dato> </columna> <columna> <titulo>Pref. tel.</titulo> <tip>Prefijo telefónico</tip> <dato>cpreftelef</dato> </columna> <columna> <titulo>Descripción</titulo> <dato>tdescripcion</dato> <recortar longitud="100" prefijo="" sufijo=" ..."/> </columna> </columnas>
vlocalidad.vnombrepais character varying(100)
.vlocalidad.vnombreprovincia character varying(50)
.vlocalidad.vnombre character varying(100)
. La columna provee acceso al master de Localidad, dado que incluye@llevaAForm="true"
y/listado/entidad/@master="si"
.vlocalidad.cpreftelef character(5)
.vlocalidad.tdescripcion text
. Se limita la longitud máxima del texto que se puede presentar en esta columna, haciendo uso del elementocolumna/recortar
. - Especificación de variedades a generar
-
Sólo se genera el listado normal y la exportación en PDF, ODS y CSV.
<salidas> <normal/> <pdf/> <ods/> <csv/> </salidas>
- Buscador personalizado
-
El buscador del listado se personaliza para ofrecer como criterios de búsqueda los nombres de país, provincia y localidad.
<!-- ... --> <configuracion> <!-- ... --> <buscador> <personalizado-simple> <texto nombre="pvnombrepais" valor="$pvnombrepais" etiqueta="País" ancho="60" teclaacceso="a"/> <texto nombre="pvnombreprovincia" valor="$pvnombreprovincia" etiqueta="Provincia" ancho="60" teclaacceso="r"/> <texto nombre="pvnombre" valor="$pvnombre" etiqueta="Nombre" ancho="60" teclaacceso="n"/> </personalizado-simple> </buscador> <!-- ... --> </configuracion> <consulta> <!-- ... --> <condiciones_de_parametros> <and> <equal> <col>vnombrepais</col> <param destacar="no">pvnombrepais</param> </equal> <like> <col>vnombreprovincia</col> <param destacar="si">pvnombreprovincia</param> </like> <like> <col>vnombre</col> <param destacar="si">pvnombre</param> </like> </and> </condiciones_de_parametros> </consulta> <!-- ... -->
Los campos del buscador se definen en el elemento personalizado-simple
. Este buscador, en particular, contiene tres campos de texto.La consulta incluye el elemento condiciones_de_parametros
, para filtrar los resultados de acuerdo a los valores que haya en los campos del buscador. Se define una condición para cada uno de los tres campos y se combinan las condiciones con AND para que el filtro considere simultáneamente todos los criterios que ingrese el usuario.Las tres condiciones comparan una columna de la consulta ( col
) con el valor de una variable (param
), definida en este caso por el campo correspondiente. El contenido del elementoparam
hace referencia al nombre del campo.
localidad_master.xml
Detalle de una localidad, con acceso a entidades dependientes.
- Definición de campos
-
Los campos que se muestran en el master están definidos como sigue:
<tabla nombre="vlocalidad"/> <!-- ... --> <campos> <campo> <titulo>Provincia</titulo> <dato>vnombreprovincia</dato> </campo> <campo> <titulo>Nombre</titulo> <dato>vnombre</dato> </campo> <campo> <titulo>Prefijo telefónico</titulo> <dato>cpreftelef</dato> </campo> <campo> <titulo>Descripción</titulo> <dato>tdescripcion</dato> </campo> </campos>
- Acceso a entidades dependientes
-
En este modelo, la entidad
localidad
tiene como dependiente a la entidadfotolocalidad
. En el master se usa el elementodetails
para proveer acceso a las Fotos de la Localidad que se está consultando.<details> <detail> <descripcion>Fotos</descripcion> <entidad>fotolocalidad</entidad> <campos> <campo detail="nlocalidad" master="nlocalidad"/> </campos> </detail> </details>
La especificación usada para
detail
, sin incluir el atributodetail/@listado="true"
, produce como resultado un botón en la parte inferior de la página, que lleva al listado de la entidad indicada —fotolocalidad_listado.php
en este caso. El mismo resultado se obtiene incluyendodetail/@tradicional="true"
.También es posible presentar dentro del mismo master el listado de la entidad dependiente, agregando a la especificación el atributo
detail/@listado="true"
y opcionalmente usando el atributodetail/@tipo-listado
para indicar el tipo de listado a usar.
paginaapp_formulario.xml
Formulario para establecer los roles que tienen permiso de acceso a una página de la aplicación. Debe recibir como parámetro el identificador de la página y opera sólo sobre las asociaciones entre ella y los roles, sin hacer modificaciones a los datos de la página en sí.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="pagina" esquema="seguridad"/> <clave> <campo>naplicacion</campo> <campo>npagina</campo> </clave> <campos> <campo tipo="Oculto"> <dato>naplicacion</dato> </campo> <campo tipo="SoloLectura"> <titulo>Página</titulo> <dato>npagina</dato> </campo> <campo tipo="SoloLectura"> <titulo>Descripción</titulo> <dato>tdescripcion</dato> </campo> <campo tipo="SeleccionableMultiple" filas="10"> <titulo>Roles con permiso</titulo> <dato>id_rolfs</dato> <entidad>rolf</entidad> <seleccionar>id_rolf</seleccionar> <descripcion>desc_rolf</descripcion> <consulta-sm> <tabla>permiso_pagina</tabla> <valor>id_rolf</valor> <where>naplicacion = '$naplicacion' AND npagina = '$npagina'</where> </consulta-sm> </campo> </campos>
seguridad.pagina.npagina character(100) NOT NULL
. Se muestra como texto plano por ser de tipo "SoloLectura".seguridad.pagina.tdescripcion text
. Se muestra como texto plano por ser de tipo "SoloLectura".Ver a continuación. - Campo SeleccionableMultiple
-
Permite seleccionar los roles.
<campo tipo="SeleccionableMultiple" filas="10"> <titulo>Roles con permiso</titulo> <dato>id_rolfs</dato> <entidad>rolf</entidad> <seleccionar>id_rolf</seleccionar> <descripcion>desc_rolf</descripcion> <consulta-sm> <tabla>permiso_pagina</tabla> <valor>id_rolf</valor> <where>naplicacion = '$naplicacion' AND npagina = '$npagina'</where> </consulta-sm> </campo>
El campo id_rolfs
no existe en la base de datos; sus valores se obtienen como resultado de la ejecución decampo/consulta-sm
.La acción "Seleccionar" del campo abre rolf_listado_seleccion_m.php
.La consulta SQL definida por campo/consulta-sm
indica los valores que tiene el campo cuando se carga el formulario. Los valores y claves de los items iniciales se obtienen a partir de una consulta SQL que combinacampo/consulta-sm
,campo/tabla
ocampo/entidad
,campo/descripcion
ycampo/seleccionar
:SELECT id_rolf, desc_rolf FROM rolf WHERE id_rolf IN (SELECT DISTINCT id_rolf FROM permiso_pagina WHERE naplicacion = '$naplicacion' AND npagina = '$npagina')
(en general:SELECT {campo/seleccionar}, {campo/descripcion} FROM {campo/tabla|campo/entidad} WHERE {campo/seleccionar} IN (SELECT DISTINCT {campo/consulta-sm/valor} FROM {campo/consulta-sm/tabla} WHERE {campo/consulta-sm/where})
).El campo se genera como un elemento HTML
select
con el atributomultiple
establecido y nombre"{campo/dato}[]"
, es decir:<select multiple="multiple" name="id_rolfs[]" ...> <!-- lista de elementos option --> </select>
XGAP no provee un procesamiento predeterminado para los valores seleccionados en el campo, sino que es responsabilidad de la aplicación utilizarlos de acuerdo a la funcionalidad que se desee proveer. Cuando se envía el formulario y la página recibe la solicitud POST resultante, los valores de
id_rolf
correspondientes a los roles seleccionados quedan disponibles en un arreglo que se puede acceder en diversas ubicaciones de código personalizado, como por ejemplo en la variable$registro['id_rolfs']
que está disponible en/formulario/custom_update
(ver a continuación).
-
Código de actualización personalizado (
/formulario/custom_update
) -
Como se mencionó al comienzo de esta sección, la funcionalidad del formulario no consiste en actualizar un único registro de la base de datos, sino las asociaciones entre la página indicada y los roles seleccionados. Esto significa que se debe redefinir el comportamiento predeterminado de la operación de actualización; por otro lado, no es necesario alterar la operación de agregado, porque este formulario no se usa con ese fin.
Para realizar la actualización se hace uso del método
iSeguridad::actualizarPermisosPorPagina($aplicacion, $pagina, $roles_usuario, $operaciones)
, provisto por el objeto$objeto_seguridad
. El parámetro$aplicacion
corresponde al valor del camponaplicacion
, el parámetro$pagina
al camponpagina
y el parámetro$roles_usuario
al array construído con las claves de los roles seleccionados en el campoid_rolfs
; el parámetro$operaciones
no se utiliza en este caso.<custom_update> <![CDATA[ $objeto_seguridad->actualizarPermisosPorPagina( $registro['naplicacion'], $registro['npagina'], $registro['id_rolfs'], '' ); ]]> </custom_update>
paginaapp_listado.xml
Listado de páginas que componen la aplicación y permisos de acceso por página.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<consulta> <select><!-- ... --></select> <from>seguridad.vpaginarolfs</from> <!-- ... --> </consulta> <columnas> <columna llevaAForm="true"> <titulo>Página</titulo> <dato>npagina</dato> </columna> <columna> <titulo>Descripción</titulo> <dato>tdescripcion</dato> </columna> <columna> <titulo>Tipo</titulo> <dato>ctipo</dato> <formato> <codigo> <![CDATA[ return isset($GLOBALS['tipos_pag'][$valor]) ? $GLOBALS['tipos_pag'][$valor] : ' '; ]]> </codigo> </formato> </columna> <columna> <titulo>Roles</titulo> <tip>Roles con permiso de acceso a la página</tip> <dato>desc_rolfs</dato> </columna> </columnas>
seguridad.vpaginarolfs.npagina character(100)
.seguridad.vpaginarolfs.tdescripcion text
.seguridad.vpaginarolfs.ctipo
. Ver a continuación.seguridad.vpaginarolfs.desc_rolfs text
. El valor de esta columna se construye en la vistaseguridad.vpaginarolfs
, como la concatenación de los nombres de los roles que tienen permiso de acceso a la página. Su definición es:SELECT ..., array_to_string( ARRAY( SELECT btrim(r.desc_rolf) AS desc_rolf FROM seguridad.permiso_pagina pp JOIN seguridad.rolf r USING (id_rolf) WHERE p.naplicacion = pp.naplicacion AND p.npagina = pp.npagina ORDER BY r.desc_rolf ), ', ') AS desc_rolfs FROM seguridad.pagina p;
- Columna con formato personalizado
-
En la columna
ctipo
, que corresponde al identificador interno de tipo de página, se usacolumna/formato/codigo
para mostrar la descripción del tipo en lugar de dicho identificador. El código de formato utiliza el array$GLOBALS['tipos_pag']
, construído en otra sección de código, que mapea cada identificador con su descripción.<?php return isset($GLOBALS['tipos_pag'][$valor]) ? $GLOBALS['tipos_pag'][$valor] : ' ';
- Especificación de variedades a generar
-
Sólo se genera el listado normal y la exportación en PDF, ODS y XLS.
<salidas> <normal/> <pdf/> <ods/> <xls/> </salidas>
- Buscador personalizado
-
El buscador del listado se personaliza para permitir la búsqueda por Página, Descripción, Tipo y/o Roles. Los controles de búsqueda se implementan como campos de texto, excepto el Tipo, que se presenta como una lista desplegable.
<configuracion> <!-- ... --> <buscador> <personalizado-simple> <texto nombre="pnpagina" valor="$pnpagina" etiqueta="Página" ancho="60" teclaacceso="p" mayusculas="false"/> <texto nombre="ptdescripcion" valor="$ptdescripcion" etiqueta="Descripción" ancho="60" teclaacceso="d" mayusculas="false"/> <combo nombre="pctipo" actual="$pctipo" etiqueta="Tipo" teclaacceso="t"> <variable>tipos_pag</variable> </combo> <texto nombre="pdesc_rolfs" valor="$pdesc_rolfs" etiqueta="Roles" ancho="60" teclaacceso="r"/> </personalizado-simple> </buscador> <!-- ... --> </configuracion> <consulta> <!-- ... --> <condiciones_de_parametros> <and> <!-- ... --> <like> <col>npagina</col> <param destacar="si">pnpagina</param> </like> <like> <col>tdescripcion</col> <param destacar="si">ptdescripcion</param> </like> <equal> <col>ctipo</col> <param>pctipo</param> </equal> <like> <col>desc_rolfs</col> <param destacar="si">pdesc_rolfs</param> </like> </and> </condiciones_de_parametros> </consulta>
Los valores de los campos pnpagina
,ptdescripcion
ypdesc_rolfs
se aplican como filtro a las columnasnpagina
,tdescripcion
ydesc_rolfs
, respectivamente, utilizando el operador SQLLIKE
.Los valores de la lista desplegable están dados por el array $tipos_pag
, construído en una sección de código extra.El campo pctipo
sólo permite la selección de un tipo por vez, por lo cual el filtro consiste en aplicar el operador SQL=
entre el valor seleccionado y la columnactipo
.La estructura general de la definición de un buscador personalizado se encuentra más detallada en la documentación de localidad_listado.xml.
- Acciones
-
El listado provee dos acciones:
-
“Actualizar índice de páginas” causa que se cargue la página guardarindicepaginas_contenido.php, donde está implementada la actualización del índice de páginas en la base de datos.
<configuracion> <!-- ... --> <acciones> <accion nombre="updind"> <descripcion>Actualizar índice de páginas</descripcion> <destino pasarParametros="true"> <url>guardarindicepaginas_contenido.php</url> </destino> </accion> <!-- ... --> </acciones> </configuracion>
-
“Exportar permisos a CSV” permite exportar a formato CSV los permisos definidos para las páginas, haciendo una solicitud GET a la variedad CSV de permiso_pagina_export_listado.xml. La solicitud incluye el parámetro
RequestXgap::PARAMETRO_DISPOSICION_CONTENIDO_IMPRIMIBLES
con valorattachment
, lo que causa que el navegador ofrezca guardar o abrir el contenido de la respuesta, sin salir de la página actual.<configuracion> <!-- ... --> <acciones> <!-- ... --> <accion nombre="exportar_permisos_csv"> <descripcion>Exportar permisos a CSV</descripcion> <tip>Emite un archivo CVS que contiene todos los permisos definidos</tip> <destino historial="skip" pasarParametros="false" tipo-request="GET"> <url>permiso_pagina_export_listado_csv.php</url> <parametros> <parametro nombre-expr="RequestXgap::PARAMETRO_DISPOSICION_CONTENIDO_IMPRIMIBLES" valor="attachment"/> </parametros> </destino> </accion> </acciones> </configuracion>
-
-
Código extra PHP en
xgap_cargado
-
Incluye el archivo
clases/PropiedadesSistema.inc.php
, el cual define la clasePropiedadesSistema
, que se utiliza en otras secciones de código.<?php require 'clases/PropiedadesSistema.inc.php';
-
Código extra PHP en
antes_consulta
-
Define variables que se usan, directa o indirectamente, para filtrar los resultados de la consulta.
<?php $GLOBALS['aplicacion_q'] = "'" // . $params['conexion']->qstr(Request::obtener('aplicacion', XGAP_CONF_APLICACION)) . "'"; $GLOBALS['cte_seguridad'] = iSeguridad::PAGINA_USO_CON; // Seguridad::obtenerInformacionSeguridad($opciones_pagina, $descripciones_pagina, $sufijos_pagina); $tipos_pag = array('' => 'Todas las Páginas'); asort($descripciones_pagina, SORT_STRING); foreach ($descripciones_pagina as $pag => $desc) { $tipos_pag[$pag] = $desc; } $GLOBALS['tipos_pag'] = $tipos_pag; //
Las variables $aplicacion_q
y$cte_seguridad
se usan enconsulta/condiciones_de_parametros
para que sólo se muestren las páginas que pertenecen a la aplicación indicada como parámetroaplicacion
en la solicitud y que tienen seguridad habilitada (iSeguridad::PAGINA_USO_CON
), respectivamente.<consulta> <!-- ... --> <condiciones_de_parametros> <and> <equal> <col>naplicacion</col> <constante>$aplicacion_q</constante> </equal> <equal> <col>nseguridad</col> <constante>$cte_seguridad</constante> </equal> <!-- ... -->
En este caso se decidió utilizar consulta/condiciones_de_parametros
para incluir en la consulta las dos condiciones mencionadas, pero también se podrían haber definido explícitamente enconsulta/where
.Ya se mencionó la variable $tipos_pag
, que se usa para definir los items en la lista desplegable del campopctipo
en el buscador personalizado y para proveer la descripción del tipo en el formato de la columnactipo
. -
Código extra PHP en
despues_inicializacion
-
Determina si es necesario actualizar el índice de páginas y registra este hecho en la variable booleana
$index_update_needed
.<?php $conexion = $params['conexion']; $time_last_index_update = (int) PropiedadesSistema::obtenerValor( $conexion, PropiedadesSistema::PROP_ULT_ACT_INDICE_PAGS ); $time_current_index = @filemtime(NOMBRE_INDICE); $GLOBALS['index_update_needed'] = !is_null($time_last_index_update) && $time_current_index !== false && $time_current_index > $time_last_index_update;
-
Código extra PHP en
antes_tabla_datos
-
Si es necesario actualizar el índice de páginas, según lo indicado por la variable
$index_update_needed
definida endespues_inicializacion
, agrega un mensaje de alerta para informar del hecho al usuario.<?php if ($GLOBALS['index_update_needed']) { Mensaje::alerta('Es necesario actualizar el índice de páginas.'); }
El agregado del mensaje también se podría haber implementado a través del mecanismo de mensajes de página, con código en
despues_inicializacion
, en lugar deantes_tabla_datos
:<codigo tipo="PHP" ubicacion="despues_inicializacion"> <![CDATA[ <?php // ... global $index_update_needed; $index_update_needed = ... if ($index_update_needed) { Pagina::instancia()->mensajes()->agregar( ObjetoMensaje::nuevo( 'Es necesario actualizar el índice de páginas.', Mensaje::TIPO_ALERTA ) ); }
-
Código extra JAVASCRIPT en
fin_body
-
Si es necesario actualizar el índice de páginas, según lo indicado por la variable
$index_update_needed
definida endespues_inicializacion
, invoca automáticamente la acción “Actualizar índice de páginas” cuando se carga la página, previa confirmación por parte del usuario.<?php if ($GLOBALS['index_update_needed']) { ?> jQuery(document).ready(function() { var update_now = confirm( "El índice de páginas no está actualizado. ¿Desea actualizarlo ahora?" ); if (update_now) { jQuery('#b_accion_updind').click(); } }); <?php } ?>
pais_formulario.xml
Formulario de alta/baja/modificación de país.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="pais"/> ... <campos> <campo tipo="Oculto"> <dato>npais</dato> </campo> <campo tipo="ComboBD"> <titulo>Continente</titulo> <dato>ncontinente</dato> <consulta> <tabla>continente</tabla> <valor>ncontinente</valor> <etiqueta>vnombre</etiqueta> <orderby>vnombre</orderby> </consulta> <opcion-combobd-seleccion-vacia texto="[no especificado]"/> </campo> <campo ancho="60"> <titulo>Nombre</titulo> <dato>vnombre</dato> </campo> <campo> <titulo>Código</titulo> <dato>ccod2</dato> </campo> <campo> <titulo>Prefijo telefónico</titulo> <dato>cpreftelef</dato> <validacion>unsigned</validacion> </campo> <campo tipo="Archivo"> <dato>vbandera</dato> <titulo>Bandera</titulo> <tip>Archivo de imagen para la bandera</tip> <destino>pais</destino> </campo> <campo tipo="Archivo"> <dato>vhimno</dato> <titulo>Himno</titulo> <tip>Archivo de audio para el himno</tip> <destino>pais</destino> </campo> </campos>
Valor: pais.ncontinente integer
. Items en la lista desplegable:continente.ncontinente integer
para los valores;continente.vnombre character varying(50)
para las etiquetas.La definición usa
campo/opcion-combobd-seleccion-vacia/@texto
para establecer explícitamente el texto del item en la lista que indica que no se selecciona un valor.pais.vnombre character varying(100) NOT NULL
. El ancho del campo de texto se limita a 60 caracteres, en vez de los 100 que tomaría por defecto de acuerdo a la definición de la columna en la base de datos, para que no ocupe demasiado espacio horizontal.pais.ccod2 character(2)
.pais.cpreftelef character(3)
.pais.vbandera character varying(2048)
.pais.vhimno character varying(2048)
La ruta del archivo subido, tanto para este campo como para el anterior, se modifica en código extra. -
Código extra PHP en
antes_procesar_uploads
-
Modifica la ruta de los archivos subidos, concatenándole la clave primaria del país, para que los archivos de cada país queden almacenados en un directorio separado.
<?php if (empty2($params['registro']['npais'], false)) { $npais = $params['conexion']->siguienteId('pais_npais_seq'); $params['registro']['npais'] = $npais; } else { $npais = $params['registro']['npais']; } foreach ($params['campos'] as $dato => $info) { if ($info['conservar'] === false && isset($info['upload'])) { $nuevo_dir_destino = $info['destino'] != '' ? ruta_archivo(array($info['destino'], $npais), DIR_SEP, false) : "pais-$npais"; $info['upload']->cambiarDirDestino($nuevo_dir_destino, true); } }
pais_listado.xml
Listado de países.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<columnas> <columna llevaAForm="true"> <titulo>Nombre</titulo> <dato>vnombre</dato> </columna> <columna> <titulo>Código</titulo> <dato>ccod2</dato> </columna> <columna> <titulo>Pref.</titulo> <tip>Prefijo telefónico</tip> <dato>cpreftelef</dato> </columna> <columna esArchivo="true" permiteOrdenar="false"> <dato>vhimno</dato> <titulo>Himno</titulo> </columna> <columna permiteOrdenar="false"> <dato>vbandera</dato> <titulo>Bandera</titulo> <imagen> <miniatura alto="32"/> </imagen> </columna> </columnas>
pais.vnombre character varying(100)
.pais.ccod2 character(2)
.pais.cpreftelef character(3)
.pais.vhimno character varying(2048)
.pais.vbandera character varying(2048)
. Columna de imágenes, que muestra miniaturas de las banderas, con una altura de 32px. - Especificación de variedades a generar
-
Se incluye la generación de CSV y se excluye la de XLS.
<salidas> <normal/> <seleccion/> <pdf/> <ods/> <csv/> </salidas>
permiso_pagina_export_listado.xml
Listado usado para exportar a formato CSV los permisos de páginas de la aplicación. La generación no incluye ninguna variante HTML, dado que la única función de este listado es proveer exportación de datos.
- Definición de consulta y columnas
-
La consulta obtiene todas las filas de la tabla
seguridad.permiso_pagina
que corresponden a la aplicación actual, mientras que las columnas del listado están definidas como un mapeo directo a las columnas que se quieren exportar de dicha tabla.<consulta> <select>naplicacion, npagina, id_rolf</select> <from>seguridad.permiso_pagina</from> <where>naplicacion = '{$GLOBALS['naplicacion']}'</where> <order_by>npagina, id_rolf</order_by> </consulta> <!-- ... --> <columnas> <columna> <titulo>naplicacion</titulo> <dato>naplicacion</dato> </columna> <columna> <titulo>npagina</titulo> <dato>npagina</dato> </columna> <columna> <titulo>id_rolf</titulo> <dato>id_rolf</dato> </columna> </columnas>
La variable $GLOBALS['naplicacion']
se define en código extra. - Especificación de variedades a generar
-
La salida de este listado está limitada a la variedad CSV.
<salidas> <csv/> </salidas>
-
Código extra PHP en
antes_consulta
-
Define la variable global
$naplicacion
, que se usa para filtrar la consulta del listado.<?php $GLOBALS['naplicacion'] = XGAP_CONF_APLICACION;
-
Código extra PHP en
despues_inicializacion
-
Establece un valor personalizado para el nombre de archivo que se va a sugerir al usuario, formado con el nombre de la aplicación y la fecha y hora de generación.
<?php $params['nombre_archivo'] = XGAP_CONF_APLICACION . '-permisos_pagina-' . date('YmdHis');
provincia_formulario.xml
Formulario de alta/baja/modificación de provincia.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="provincia"/> ... <campos> <campo tipo="Seleccionable" ancho="60"> <titulo>País</titulo> <dato>npais</dato> <entidad>pais</entidad> <seleccionar>npais</seleccionar> <descripcion>vnombre</descripcion> </campo> <campo> <titulo>Nombre</titulo> <dato>vnombre</dato> </campo> <campo> <titulo>Código</titulo> <dato>ccodigo</dato> </campo> </campos>
provincia.npais integer NOT NULL
.provincia.vnombre character varying(50) NOT NULL
.provincia.ccodigo character(2)
.
provincia_listado.xml
Listado de provincias.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<columnas> <columna> <titulo>País</titulo> <dato>vnombrepais</dato> </columna> <columna llevaAForm="true"> <titulo>Nombre</titulo> <dato>vnombre</dato> </columna> <columna> <titulo>Código</titulo> <dato>ccodigo</dato> </columna> </columnas>
vprovincia.character varying(100)
.vprovincia.character varying(50)
.vprovincia.ccodigo character(2)
. - Especificación de variedades a generar
-
Se incluye la generación de CSV y se excluye la de XLS.
<salidas> <normal/> <seleccion/> <pdf/> <ods/> <csv/> </salidas>
- Buscador personalizado
-
El buscador del listado se personaliza para permitir la búsqueda en cada una de las columnas por separado.
<!-- ... --> <configuracion> <!-- ... --> <buscador> <personalizado-simple> <texto nombre="pvnombrepais" valor="$pvnombrepais" etiqueta="País" ancho="60" teclaacceso="a"/> <texto nombre="pvnombre" valor="$pvnombre" etiqueta="Nombre" ancho="60" teclaacceso="n"/> <texto nombre="pccodigo" valor="$pccodigo" etiqueta="Código" ancho="2" teclaacceso="c"/> </personalizado-simple> </buscador> <!-- ... --> </configuracion> <consulta> <!-- ... --> <condiciones_de_parametros> <and> <like> <col>vnombrepais</col> <param destacar="si">pvnombrepais</param> </like> <like> <col>vnombre</col> <param destacar="si">pvnombre</param> </like> <equal> <col>ccodigo</col> <param>pccodigo</param> </equal> </and> </condiciones_de_parametros> </consulta> <!-- ... -->
Los valores de los campos
pvnombrepais
ypvnombre
se aplican como filtro a las columnasvnombrepais
yvnombre
, respectivamente, utilizando el operador SQLLIKE
, en tanto que el valor del campopccodigo
se compara con la columnaccodigo
mediante el operador SQL=
.La estructura general de la definición de un buscador personalizado se encuentra más detallada en la documentación de localidad_listado.xml.
rol_compu_usu_formulario.xml
Formulario de modificación de roles funcionales asignados a un usuario.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="vrol_compu_usu" esquema="seguridad"/> ... <campos> <campo tipo="Oculto"> <dato>id_rolh</dato> </campo> <campo tipo="Oculto"> <dato>id_rolv</dato> </campo> <campo tipo="SoloLectura"> <dato>id_usuario</dato> <titulo>Usuario</titulo> <consulta> <tabla>usuario</tabla> <valor>id_usuario</valor> <etiqueta>nombre</etiqueta> </consulta> </campo> <campo tipo="SeleccionableMultiple" filas="6"> <dato>id_rolfs</dato> <titulo>Roles</titulo> <entidad>rolf</entidad> <seleccionar>id_rolf</seleccionar> <descripcion>desc_rolf</descripcion> <etiqueta>Seleccionar</etiqueta> <consulta-sm> <tabla>rol_compu_usu</tabla> <valor>id_rolf</valor> <where>id_usuario = $id_usuario</where> </consulta-sm> </campo> </campos>
Campo SoloLectura
que muestra el valor deseguridad.usuario.nombre
correspondiente aseguridad.usuario.id_usuario = seguridad.vrol_compu_usu.id_usuario
, por el uso decampo/consulta
.Campo SeleccionableMultiple
que permite elegir los roles del usuario. Los valores seleccionados se procesan en las operaciones personalizadas del formulario.La documentación del
SeleccionableMultiple
en paginaapp_formulario.xml contiene una explicación más detallada sobre el funcionamiento de este tipo de campo. -
Código extra PHP en
inicio_pagina
-
Define una función que se usa en las operaciones personalizadas, para evitar la duplicación de código.
<?php function extender_registro(&$registro, $id_rolfs) { $registro['roles'] = !empty($id_rolfs) ? implode("|", $id_rolfs) : null; }
-
Código personalizado para agregado y actualización (
/formulario/custom_insert
y/formulario/custom_update
) -
Los dos bloques de código son similares: establecen el campo
roles
del registro, con la concatenación de los IDs de los roles seleccionados, e invocan aConexion::AutoExecute()
para realizar la operación correspondiente.<custom_insert> <![CDATA[ extender_registro($registro, $id_rolfs); $objeto_conexion->AutoExecute( 'seguridad.vrol_compu_usu', $registro, 'INSERT' ); ]]> </custom_insert> <custom_update> <![CDATA[ extender_registro($registro, $id_rolfs); $objeto_conexion->AutoExecute( 'seguridad.vrol_compu_usu', $registro, UPDATE', $xclave ); ]]> </custom_update>
El proceso se completa en la base de datos, a través de dos reglas asociadas a la vista
seguridad.vrol_compu_usu
, las cuales se encargan de registrar las asociaciones de acuerdo a los valores del camporoles
del registro.CREATE FUNCTION seguridad.fins_rol_compu_usu( pid_usuario bigint, pid_rolh integer, pid_rolv integer, proles character) RETURNS void AS $BODY$ DECLARE i integer; rol_f bpchar; rol_f_int integer; BEGIN i := 1; rol_f := split_part(proles, '|', i); WHILE ((NOT rol_f IS NULL) AND (rol_f <> '')) LOOP rol_f_int = CAST(rol_f AS INTEGER); INSERT INTO rol_compu_usu(id_rolh, id_rolv, id_rolf, id_usuario) VALUES (pid_rolh, pid_rolv, rol_f_int, pid_usuario); i := i + 1; rol_f := split_part(proles, '|', i); END LOOP; END; $BODY$ LANGUAGE 'plpgsql' VOLATILE; CREATE RULE ri_vrol_compu_usu AS ON INSERT TO seguridad.vrol_compu_usu DO INSTEAD SELECT seguridad.fins_rol_compu_usu( new.id_usuario::bigint, new.id_rolh, new.id_rolv, new.roles::bpchar ) AS fins_rol_compu_usu; CREATE RULE ru_vrol_compu_usu AS ON UPDATE TO seguridad.vrol_compu_usu DO INSTEAD ( DELETE FROM seguridad.rol_compu_usu WHERE rol_compu_usu.id_usuario = old.id_usuario; SELECT seguridad.fins_rol_compu_usu( new.id_usuario::bigint, new.id_rolh, new.id_rolv, new.roles::bpchar ) AS fins_rol_compu_usu; );
rol_compu_usu_listado.xml
Listado de asignaciones de roles funcionales a usuarios.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<columnas> <columna llevaAForm="true"> <titulo>Usuario</titulo> <dato>nombre</dato> </columna> <columna> <titulo>Roles</titulo> <dato>desc_rolfs</dato> </columna> </columnas>
seguridad.vrol_compu_usu.nombre character(10)
.seguridad.vrol_compu_usu.desc_rolfs text
. Esta columna muestra la lista de todos los roles asignados al usuario. Su definición en la vista es la siguiente:SELECT -- ... array_to_string( ARRAY(SELECT btrim(rc.desc_rol_comp) AS desc_rol_comp_trim FROM seguridad.rol_compu_usu rcu1 JOIN seguridad.rol_compuesto rc ON rcu1.id_rolv = rc.id_rolv AND rcu1.id_rolh = rc.id_rolh AND rcu1.id_rolf = rc.id_rolf WHERE rcu1.id_usuario = u.id_usuario ORDER BY rc.desc_rol_comp), ', ' ) AS desc_rolfs -- ...
rolf_formulario.xml
Formulario de alta y modificación de roles funcionales.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="rolf" esquema="seguridad"/> ... <campos> <campo> <titulo>Descripción del rol</titulo> <dato>desc_rolf</dato> </campo> <campo ancho="60"> <titulo>Página de inicio</titulo> <dato>pagina_inicio</dato> </campo> </campos>
seguridad.rolf.desc_rolf character varying(30) NOT NULL
.seguridad.rolf.pagina_inicio character varying(1024)
.
rolf_listado.xml
Listado de roles funcionales.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<columnas> <columna llevaAForm="true"> <titulo>Descripción</titulo> <dato>desc_rolf</dato> </columna> <columna> <titulo>Página de inicio</titulo> <dato>pagina_inicio</dato> </columna> </columnas>
seguridad.rolf.desc_rolf character varying(30)
.seguridad.rolf.pagina_inicio character varying(1024)
. - Especificación de variedades a generar
-
En este listado se debe incluir la generación de la variedad
seleccion_m
, que se utiliza en paginaapp_formulario.xml y rol_compu_usu_formulario.xml.<salidas> <normal/> <seleccion/> <seleccion_m/> <pdf/> </salidas>
seguridad_contenido.xml
Página para establecer los permisos de página por rol funcional.
- Comandos
-
El comando “Establecer Permisos” realiza la actualización de los permisos seleccionados por el usuario.
<configuracion> <!-- ... --> <comandos-genericos> <comando nombre="b_aplicar" texto="Establecer Permisos" clase="ButtonGuardar" codigo="enviar_accion("accion", "actualizar", "form_seguridad");"> <condicion> <![CDATA[ global $errores; return count($errores) == 0; ]]> </condicion> <preparar> <![CDATA[ global $hay_rol; $habilitado = $hay_rol; ]]> </preparar> </comando> </comandos-genericos> </configuracion>
El código del comando realiza el envío del formulario form_seguridad
, causando una solicitud POST a la misma página, que se procesa en el código incluido enpagina/contenido-anterior
.El código en el elemento comando/condicion
hace que el comando no se muestre si hubo algún error durante el procesamiento de la solicitud. El valor de la variable$errores
se establece en el código incluido enpagina/contenido-anterior
.El código en el elemento comando/preparar
deshabilita el comando si no hay un rol seleccionado. El valor de la variable$hay_rol
se establece en el código incluido enpagina/contenido-anterior
.
usuario_cambioclave_formulario.xml
Formulario de cambio de contraseña del usuario actual.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="usuario" esquema="seguridad"/> ... <campos> <campo tipo="Sesion"> <dato>id_usuario</dato> <valor>ident_usuario</valor> </campo> <campo tipo="SoloLectura"> <titulo>Nombre Corto</titulo> <dato>nombre</dato> </campo> <campo tipo="SoloLectura"> <titulo>Nombre Completo</titulo> <dato>nombre_ext</dato> </campo> <!-- Los dos campos siguientes están definidos con elementos xi:include, en el código original. Se transcribe el código incluído, para facilitar la lectura. --> <campo tipo="Clave" ancho="40" requerido="true"> <titulo>Contraseña</titulo> <dato>contrasenia</dato> </campo> <campo tipo="Clave" ancho="40" requerido="true"> <titulo>Contraseña (nuevamente)</titulo> <dato>contrasenia2</dato> </campo> </campos>
seguridad.usuario.nombre character(10)
.seguridad.usuario.nombre_ext character varying(30)
seguridad.usuario.contrasenia character varying(40)
No corresponde a una columna en la base de datos. Sólo se utiliza para confirmar la contraseña ingresada. -
Código extra PHP en
despues_inicializacion
-
Asegura que el formulario no se pueda presentar en modo de agregado.
<?php // Este formulario no se debe usar para agregados. if ($params['agregado']) { Http::redirigirAError(NULL, Mensaje::COD_ERR_EJEC_ACCION_DESHABILITADA); }
-
Código extra JavaScript en
fin_body
-
Definido en usuario_formulario.xml.
- Uso de XInclude
-
Este formulario incluye desde
usuario_formulario.xml
los elementos que ambos tienen en común, mediante XInclude, para minimizar la repetición de código.<formulario xmlns:xi="http://www.w3.org/2001/XInclude" > <!-- ... --> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/configuracion)"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/tabla)"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/clave)"/> <campos> <!-- ... --> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='contrasenia'])"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='contrasenia2'])"/> </campos> <codigoExtra> <!-- ... --> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/codigoExtra/codigo[@tipo='JAVASCRIPT' and @ubicacion='fin_body'])"/> </codigoExtra> </formulario>
usuario_cambioclaveadmin_formulario.xml
Formulario de cambio de contraseña de un usuario.
Este formulario es muy similar a usuario_cambioclave_formulario.xml. La única diferencia sustancial entre ambos es que usuario_cambioclave_formulario.xml
define un campo de tipo Sesion
para establecer el valor del identificador de usuario (y por lo tanto siempre corresponde al usuario actual), en tanto que usuario_cambioclaveadmin_formulario.xml
recibe este dato a través de un parámetro de la solicitud.
- Uso de XInclude
-
Para aprovechar la similitud mencionada, la mayor parte del código de
usuario_cambioclaveadmin_formulario.xml
se obtiene deusuario_formulario.xml
yusuario_cambioclave_formulario.xml
por medio de elementos XInclude:<formulario xmlns:xi="http://www.w3.org/2001/XInclude" > <!-- ... --> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/configuracion)"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/tabla)"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/clave)"/> <campos> <xi:include href="usuario_cambioclave_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='nombre'])"/> <xi:include href="usuario_cambioclave_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='nombre_ext'])"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='contrasenia'])"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='contrasenia2'])"/> </campos> <codigoExtra> <xi:include href="usuario_cambioclave_formulario.xml" xpointer="xpointer(/formulario/codigoExtra/codigo[@tipo='PHP' and @ubicacion='despues_inicializacion'])"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/codigoExtra/codigo[@tipo='JAVASCRIPT' and @ubicacion='fin_body'])"/> </codigoExtra> </formulario>
-
Código extra PHP en
despues_inicializacion
-
Definido en usuario_cambioclave_formulario.xml.
-
Código extra JavaScript en
fin_body
-
Definido en usuario_formulario.xml.
usuario_formulario.xml
Formulario de alta y modificación de usuario.
- Definición de campos
-
Los campos del formulario están definidos como sigue:
<tabla nombre="usuario" esquema="seguridad"/> ... <campos> <campo mayusculas="false"> <titulo>Nombre corto</titulo> <dato>nombre</dato> <tip>Nombre para el ingreso al sistema. Sólo debe usar letras, números o guiones bajos.</tip> <validacion>alphanum</validacion> </campo> <campo> <titulo>Nombre completo</titulo> <dato>nombre_ext</dato> <tip>Apellido y nombre completos</tip> </campo> <campo tipo="Clave" ancho="40" requerido="true"> <titulo>Contraseña</titulo> <dato>contrasenia</dato> </campo> <campo tipo="Clave" ancho="40" requerido="true"> <titulo>Contraseña (nuevamente)</titulo> <dato>contrasenia2</dato> </campo> <separador_grupo titulo="Estado" /> <campo tipo="RadioBD"> <titulo>Habilitado</titulo> <dato>habilitado</dato> <consulta> <tabla>sistema.traduccionboolean</tabla> <valor>bvalor</valor> <etiqueta>vvalortraducido</etiqueta> <where>vnombre = 'SINO'</where> </consulta> </campo> </campos>
seguridad.usuario.nombre character(10)
.seguridad.usuario.nombre_ext character varying(30)
seguridad.usuario.contrasenia character varying(40)
No corresponde a una columna en la base de datos. Sólo se utiliza para confirmar la contraseña ingresada. Valor: seguridad.usuario.habilitado boolean NOT NULL
. Opciones:sistema.traduccionboolean.bvalor boolean
para los valores;sistema.traduccionboolean.vvalortraducido character varying(50)
para las etiquetas. -
Código extra PHP en
xgap_cargado
-
Establece un valor por defecto para el usuario, cuando el formulario se presenta en modo de modificación. Ver comentario en el código.
<?php $agregado = param2boolean(Request::obtener(RequestXgap::PARAMETRO_FORM_ALTA, false)); if (!$agregado && !Request::existe('id_usuario') && ($id_usuario = Contexto::obtener('ident_usuario', '')) != '') { /* * Si es un formulario de modificación y no se indica el id_usuario en el request, * se carga el correspondiente al usuario actual */ Request::almacenarGet('id_usuario', $id_usuario, true); }
-
Código extra JavaScript en
fin_body
-
Agrega una función de pos-validación para impedir que se guarden los cambios si las dos contraseñas ingresadas no son iguales.
v.f_pos_validation = function () { if (obtener_elemento('contrasenia').value != obtener_elemento('contrasenia2').value) { alert('La contraseña ingresada difiere de su confirmación. Por favor inténtelo nuevamente.'); return false; } else return true; };
usuario_listado.xml
Listado de usuarios.
- Definición de columnas
-
Las columnas del listado están definidas como sigue:
<columnas> <columna llevaALink="true"> <titulo>Nombre corto</titulo> <dato>nombre</dato> <link>usuario_sinclave_formulario.php</link> </columna> <columna> <titulo>Nombre completo</titulo> <dato>nombre_ext</dato> </columna> <columna> <titulo>Hab?</titulo> <tip>¿Habilitado?</tip> <condicion> <![CDATA[ return !$GLOBALS['ocultar_borrados']; ]]> </condicion> <dato>vhabilitado</dato> </columna> <acciones> <titulo></titulo> <condicion> <![CDATA[ return $seguridad->puedeVerPagina('usuario_cambioclaveadmin_formulario.php'); ]]> </condicion> <accion> <destino pasarParametros="true"> <url>usuario_cambioclaveadmin_formulario.php</url> </destino> <etiqueta>Cambiar contraseña</etiqueta> </accion> </acciones> </columnas>
seguridad.usuario.nombre character(10)
. La definición de la columna incluye el elementocolumna/link
para especificar un formulario distinto que el predeterminado; en este caso, el link lleva al formulario de usuario sin cambio de contraseña, dado que ésta se modifica por separado.seguridad.usuario.nombre_ext character varying(30)
.seguridad.usuario.habilitado boolean
. La condición aplicada a esta columna hace que sólo sea visible cuando se están mostrando los usuarios deshabilitados, de acuerdo al estado del checkbox que se genera por la definición de borrado lógico incluída en el listado:<borrado-logico> <columna>habilitado</columna> <valor>false</valor> <con-control etiqueta="Sólo habilitados" generar="true"/> </borrado-logico>
La condición de la columna utiliza la variable booleana global
$ocultar_borrados
, definida automáticamente por XGAP, que indica el estado de presentación de items con borrado lógico:<?php return !$GLOBALS['ocultar_borrados'];
Se incluye una columna de acciones, con una única acción definida que lleva al formulario de cambio de contraseña del usuario correspondiente. La condición de la columna asegura que no sea visible si el usuario no tiene permiso para acceder al destino de la acción.
<?php return $seguridad->puedeVerPagina('usuario_cambioclaveadmin_formulario.php');
usuario_sinclave_formulario.xml
Formulario de modificación de usuario, sin cambio de contraseña.
Es similar al formulario completo de usuario, con dos diferencias:
-
No incluye los dos campos destinados al ingreso de contraseña.
-
Se impide su uso para altas (debido al punto anterior).
- Uso de XInclude
-
Para aprovechar la similitud mencionada, la mayor parte del código de
usuario_sinclave_formulario.xml
se obtiene deusuario_formulario.xml
, además de un elemento de código extra deusuario_cambioclave_formulario.xml
, por medio de elementos XInclude:<formulario xmlns:xi="http://www.w3.org/2001/XInclude" > <!-- ... --> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/configuracion)"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/tabla)"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/clave)"/> <campos> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='nombre'])"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='nombre_ext'])"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/separador_grupo[1])"/> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/campos/campo[dato='habilitado'])"/> </campos> <codigoExtra> <xi:include href="usuario_formulario.xml" xpointer="xpointer(/formulario/codigoExtra/codigo[@tipo='PHP' and @ubicacion='xgap_cargado'])"/> <xi:include href="usuario_cambioclave_formulario.xml" xpointer="xpointer(/formulario/codigoExtra/codigo[@tipo='PHP' and @ubicacion='despues_inicializacion'])"/> </codigoExtra> </formulario>
-
Código extra PHP en
xgap_cargado
-
Definido en usuario_formulario.xml.
-
Código extra PHP en
despues_inicializacion
-
Definido en usuario_cambioclave_formulario.xml.
Cambios al modelo básico de aplicación predefinido por XGAP
Los scripts SQL y páginas iniciales provistos por XGAP definen un modelo básico de aplicación. En esta sección se detallan los cambios que tiene la aplicación “Localidades” respecto a ese modelo.
- Cambios en funcionalidad
-
-
Posibilidad de definir una página de inicio diferente para cada rol funcional.
-
Los usuarios se pueden deshabilitar.
-
- Cambios en el esquema de base de datos
-
-
Se agregan tablas y vistas correspondientes al modelo propio de la aplicación, junto con otras tablas auxiliares.
-
Se agrega columna
pagina_inicio
a la tablaseguridad.rolf
. -
Se agrega columna
habilitado
a la tablaseguridad.usuario
.
-
- Cambios en las páginas predefinidas
-
-
login_contenido.xml
-
-
Se impide el ingreso de usuarios deshabilitados.
-
-
login_contenido.xml
yseleccionarrol_contenido.xml
-
-
Se tiene en cuenta la página de inicio que está definida para el rol funcional del usuario, para establecer el destino de la redirección final que realizan ambas páginas.
-
Se almacena la página de inicio en la sesión.
-
-
rolf_formulario.xml
-
-
Se agrega un campo correspondiente a la columna
seguridad.rolf.pagina_inicio
.
-
-
rolf_listado.xml
-
-
Se agrega una columna para mostrar el valor de
seguridad.rolf.pagina_inicio
.
-
-
usuario_formulario.xml
yusuario_sinclave_formulario.xml
-
-
Se agrega un campo correspondiente a la columna
seguridad.usuario.habilitado
.
-
-
usuario_listado.xml
-
-
Se agrega uso de borrado lógico, de acuerdo al valor de
seguridad.usuario.habilitado
, y una columna para mostrar el estado de habilitación del usuario.
-
-