viernes, 18 de marzo de 2022

Powershell: buscar una cadena dentro de unos ficheros recursivamente

 ¿Qué ficheros dentro de este directorio y descendientes contienen una cadena específica?


gci -recurse *.php |sls button |select -first 10

jueves, 17 de marzo de 2022

Jugando a wordle (versión tildes) con powershell

Después de probar un par de palabras en el Wordle de ayer (en versión tildes), y no dar con la palabra correcta, me decidí a tener algo de "ayuda" (la palabra "trampas" es muy fea) a partir de un diccionario.

Dado que tengo un fichero que es un listado de palabras en español, con una palabra por línea, me gustaría aplicar un filtro que me busque las palabras que cumplen determinadas condiciones, como tener una "P" en primera posición, contener una "L" (sin especificar la posición) y que tengan una determinada longitud.

Esto me dará varias opciones (en el mejor de los casos, puede que sólo haya una palabra que cumpla esas condiciones, en cuyo caso esa sería la palabra buscada), y mi idea era jugar alguna de esas palabras. Por eso decía lo de "ayuda" en vez de "trampas", pero vamos, puede llamarse de cualquiera de las dos formas. Lo importante aquí es cómo se hace eso en Powershell.

Para ponérmelo un poco más difícil, y simplificar el problema, no voy a exigir que se excluyan las palabras que no tienen alguna letra de las fallidas (es decir, si he puesto la T y esta letra no se incluye, NO voy a pedir que en el listado se excluya la T), tan sólo voy a exigir que se incluyan las letras que SÍ he acertado, y en las posiciones acertadas.

Mi fichero con la lista de palabras en español se llama words.es.txt.

Wordle con tildes: https://wordle.danielfrg.com/tildes/


 

Después de probar PLÁTANO, las letras acertadas están en las posiciones 0 (P) y 2 (L).

Otras letras acertadas, aunque estarán en otras posiciones, son la A y la O.

Lo interesante aquí es fijarse en el filtro para poner condiciones, que se pone con  el comando "Where-Object" seguido de una expresión entre {llaves}, aunque tiene un alias que me gusta, y es "?".

Así pues, la línea de comando powershell para listar las palabras que cumplen esas condiciones quedaría así:


gc words.es.txt |?{($_.length -eq 8) -and ($_[0] -eq 'p') -and ($_[2] -eq 'l')}|sls a |sls o

 

Curiosamente, he de filtrar por palabras de longitud 8, ya que palabras con letras especiales (eñes, tildes...) se convierten en dos letras, por ejemplo, "peldaño" y "policía" dan "peldaño" y "policía", convirtiendo palabras de 7 letras en 8.

Esto debe ser por tema de codificación diferente entre cmd.exe y powerhsell. Pero eso lo investigaré en otra ocasión.

jueves, 10 de febrero de 2022

Powershell: cómo saber qué procesos utilizan una DLL concreta

Ejecutar en la línea de comandos de Powershell:

 

tasklist /m msvcrt.dll

 

Una cosa interesante es que se puede pedir la salida en formato CSV, TABLE y LIST.

 

tasklist /m msvcrt.dll /fo csv > procesos.csv

viernes, 21 de agosto de 2020

Fecha actual, subselects y registros pasados más recientes en MariaDB

 JI se ha decidido a colaborar con este blog publicando alguna entrada de vez en cuando, siguiendo la misma filosofía del "lo explico para enterarme yo".
 
En esta su primera entrada nos habla de SELECTS y SUBSELECTS y la fecha actual en MariaDB.
 
Esperemos que sea la primera de muchas entradas futuras.



MARIADB: FECHA ACTUAL Y REGISTROS MÁS RECIENTES


Como me ha costado un rato, comparto esto por si otro estuviera en el mismo caso, lo ve más rápido y si no es exactamente lo que busca, igual le vale como punto de partida.

Tengo una base de datos en MariaDB y necesito sacar un registro con la fecha más reciente, por lo que he necesitado la fórmula curdate() para sacar la fecha actual

Como lo que quería era la factura más reciente, anterior a hoy (PROBLEMA: tenemos algunas con fecha futura), he hecho una subconsulta. Primero he seleccionado los datos que quería de la factura y he puesto que la fecha fuera igual a la mayor (más reciente) de las pasadas (en el WHERE de la subconsulta le indico que sea anterior a hoy).

SELECT id_factura, cliente, fecha

FROM facturas

WHERE fecha = (select max(fecha)

               from facturas

               where fecha < curdate())


Es sencillo, pero me ha dado un poco de quebradero de cabeza porque al usar MAX hay que poner el campo que queramos y el paréntesis pegado a la palabra porque si no da error, así me ha pasado a mí en la consulta SQL. Utilizo un servidor de Hostinger, no sé si este comportamiento será el mismo en todos, pero mejor coger una buena costumbre para evitar fallos similares.

Espero que os pueda servir.

martes, 9 de enero de 2018

Renumerar Secuencias en Oracle (ALTER SEQUENCE)

Uno de mis usuarios me ha notificado hoy un mensaje de error que le ocurre al insertar registros en una tabla. La operación de inserción da un error en la BD Oracle:

HY000 - 1 OCIStmtExecute: ORA-00001: restricción única (...) violada

Analizando la estructura de la tabla, veo que esta tiene como clave primaria un campo ID [NUMBER(7,0)], y que la operación de inserción obtiene el siguiente ID a insertar de una secuencia (CREATE SEQUENCE...). Esto me lleva a sospechar que probablemente el siguiente ID a insertar esté coincidiendo con algún ID ya existente en la tabla, cosa extraña tratándose de una secuencia, y que no debería  pasar, pero... ¡tantas cosas no deberían pasar y pasan!

Vamos con las secuencias en ORACLE.

El juego SEQUENCE



Para saber cuál será el siguiente valor que devolverá una secuencia, podemos hacer dos cosas:

1. Función NEXTVAL.


La instrucción

SELECT SEC_MI_SECUENCIA.nextval FROM dual;
=> 3601

nos devuelve el siguiente número de la secuencia. El único "problema" es que esta operación "consume" un valor de esa secuencia. Supongamos que me devuelve el valor 3601. Si vuelvo a ejecutar la operación, devolvería un nuevo valor, 3602 (asumiendo que la secuencia se incrementa en pasos de 1 unidad). Cada vez que se vuelva a ejecutar esa instrucción, estaré "gastando" un número de la secuencia. Así que, si este comportamiento no es deseable, cuidado. Esto puede ocurrir cuando tenemos tablas donde queremos TODOS los registros que se puedan ir insertando, como una tabla de registro de actividad (log), por ejemplo. Si vemos saltos en la secuencia de IDs, no sabemos si es que son "normales" o es que tal vez alguien ha eliminado registros para borrar las huellas de alguna operación.

SELECT SEC_MI_SECUENCIA.nextval FROM dual; 

=> 3609

2. Función CURRVAL

Esta alternativa permite consultar el último valor que devolvió la secuencia, pero sin generar uno nuevo. Es decir, por muchas ejecuciones que hagamos, siempre nos va a devolver el mismo valor.

select SEC_MI_SECUENCIA.currval from dual;

=> 3609

select SEC_MI_SECUENCIA.currval from dual;

=> 3609

Dicho esto, la solución a mi problema es fácil. Veo en los datos que hay registros con un ID ya coincidente con el próximo que generará la secuencia. Así que obtengo el máximo, que resulta ser

select max(id) from SEC_MI_SECUENCIA;

==> 8596

Así que voy a ALTERAR mi secuencia para que genere números a partir del 8600. Para ello, debo especificar un incremento igual a la diferencia entre el valor actual y el deseado. En mi caso, 8600 - 3609 = 4991. Así que hay que alterar la secuencia DOS VECES: una para poner un incremento de 4991, a continuación obtenemos el siguiente valor, y después volvemos a alterar la secuencia para poner el incremento en 1. Así:

ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 4991;

SELECT SEC_MI_SECUENCIA.nextval FROM dual;

ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 1;

Tras esto, ya hemos comprobado que la inserción no da problemas.

Hasta que alguien vuelva a tocar los datos...

jueves, 29 de septiembre de 2016

Recorrer una estructura de directorios descomprimiendo ficheros con clave


Tengo una copia de seguridad que es una réplica de una estructura de carpetas y subcarpetas, con varios ficheros repartidos por todo el árbol de directorios.La copia replica la estructura de carpetas en una unidad externa. Cada fichero se comprime individualmente y se cifra con una contraseña.

Un fichero y su copia.  ¿Cuál es el original?
By Alvesgaspar (Own work (own photo)) [<a href="http://www.gnu.org/copyleft/fdl.html">GFDL</a> or <a href="http://creativecommons.org/licenses/by-sa/4.0-3.0-2.5-2.0-1.0">CC BY-SA 4.0-3.0-2.5-2.0-1.0</a>], <a href="https://commons.wikimedia.org/wiki/File%3APigs_July_2008-1.jpg">via Wikimedia Commons</a>

Si se trata de recuperar un fichero específico, no hay mucho problema, copio el comprimido a la carpeta de destino y con cualquier utilidad de descompresión lo extraigo manualmente, tecleando la contraseña en el momento necesario.

Pero si quiero restaurar TODA UNA CARPETA Y EL SUBÁRBOL que cuelga de ella, ir extrayendo los ficheros uno a uno no es una opción válida (son muchos), y deseo automatizar esta operación. Para ello, necesitaré una utilidad de línea de comandos que me permita extraer un archivo individual (usaré el 7za, la utilidad de línea de comandos del 7-zip) y un script que me permita ir recorriendo el árbol de directorios uno a uno y ejecutar esta utilidad en cada uno de ellos. También tendré que ir especificando la ruta destino individualmente para cada fichero, que deberé ir construyendo dinámicamente.

En esta ocasión, estoy trabajando en Windows. Tras un rato leyendo las especificaciones del comando FOR (en una ventana de comando basta con escribir "help for"), he aquí la solución al problema planteado:

set z="c:\bin\util\7za.exe"
set d="c:\ruta\destino"
set clave=",,"

for /D /R %%i in (*.* .) do %z% x -r -o%d%%%~pi%%~ni %%i\*.zip -p%clave% -y
::~ for /D /R %%i in (*.*) do @echo de %%~pi a %d%%%~pi%%~ni

No repetiré aquí las especificaciones del comando FOR, para eso está su ayuda, que considero suficiente (aunque un poco falta de claridad, lo reconozco), pero sí daré unas breves notas:

- El /D hace referencia a que trabajaremos con directorios, y /R es para que se haga un recorrido recursivo

- El comando a ejecutar con cada directorio debe ir en la misma línea que el FOR, hasta donde yo sé. Desconozco si se puede dividir la instrucción en varias líneas, lo cual ayudaría bastante a la legibilidad, pero yo no lo he conseguido.

- Las variables de entorno (establecidas en las primeras líneas con SET) se escriben como %VAR% y las variables del FOR llevan un doble símbolo de porcentaje %%VAR por estar dentro de un fichero de script. Si estuviéramos tecleándolas directamente en una ventana de comando se escribiría un único %, así: %VAR.

- Los modificadores introducidos delante del nombre de la variable (en el comando a ejecutar) sirven para extraer parte del nombre del directorio, así ~p significa PATH (sólo la ruta, sin el nombre) ~n significa NOMBRE (nombre del directorio a procesar)

- La línea comentada (la que comienza por ::~ for)  se puede utilizar para testear el resultado antes de ejecutarlo de verdad, imprimiendo las rutas a procesar, pero sin ejecutar la operación. Para probarlo, bastaría con descomentar esta línea y comentar la anterior a ella.

En la cláusula in es importante añadir, además de *.*, el punto (.) que hace referencia al directorio actual. Si no se incluye, se procesan todos los directorios que cuelgan del actual, pero no el directorio contenedor. Tampoco pasa nada, siempre podía haber copiado la carpeta a restaurar como única carpeta hija de c:\tmp, pongamos por caso, y ejecutar el script en c:\tmp directamente. Pero incluyendo el punto la solución es más genérica, pues vale tanto si en el directorio actual hay ficheros o si sólo hay directorios.

Como reflexión, pensemos que cuando hacemos una copia de seguridad, no basta con tener los ficheros guardados, sino que debemos tener previsto (¡y probado!) el sistema que utilizaremos para restaurar la información en caso necesario. De nada nos sirve tener una copia de seguridad si, en caso de necesidad, a la hora de restaurarla nos vamos a tener que esperar una eternidad a que la información vuelva a estar disponible, por tener que llevar a cabo un proceso manual o un proceso que, aunque esté automatizado, no responda en un tiempo razonable.

Había una cita conocida que no recuerdo literalmente, pero venía a decir una cosa importantísima, algo así como:

 - "No planifiques una estrategia de copias de seguridad, planifica una estrategia de recuperación"

(si alguien sabe el autor y la frase exacta, le estaré agradecido si me lo pone en un comentario).

Ahora es buen momento para probar tu procedimiento de contingencia: ¿cuánto tardarías si tuvieras que restaurar ahora mismo alguna información importante?

jueves, 28 de julio de 2016

La crème de la crème (consultas TOP-N en Oracle)

En esta entrada hablaba de cómo seleccionar las N primeras filas de una tabla. En todos los casos (Informix, SQL Server, SQLite, Oracle), hacía una consulta básica y nos quedábamos con los 10 primeros resultados. Las filas en una tabla relacional no están ordenadas, por definición, lo cual significa que la consulta nos da las N primeras filas aleatorias, es decir, sin ordenar.

Pero la cosa cambia un poco si las queremos ordenadas, es decir los mejores resultados, las mejores notas, los mejores salarios, la crème de la crème... (o los peores).

Queremos los resultados crème de la crème

Esto significa sacar las N filas primeras (o mínimas) o las N filas últimas (o máximas), lo que se denomina a veces consultas TOP-N (TOP-N queries, por si queréis buscarlo en inglés).

En el caso de SQLite3 podíamos poner la cláusula LIMIT después del ORDER BY, como se explicaba en aquella entrada. Pero en ORACLE no existe la cláusula LIMIT, así que habrá que hacerlo de alguna otra manera.

En principio, utilizando la columna "mágica" ROWNUM, si hacemos lo siguiente:

SELECT * FROM TABLA WHERE ROWNUM <= 100

estamos obteniendo 100 filas aleatorias. Uno podría pensar que entonces, para obtener las TOP-100 filas bastaría con añadir una condición ORDER BY, tal que así:

SELECT * FROM TABLA WHERE ROWNUM <= 100 ORDER BY CAMPO

Pero eso no funcionaría. Lo que nos estaría dando son 100 filas aleatorias, y una vez seleccionadas (y no antes), nos las mostraría ordenadas por el CAMPO indicado. Pero eso no significa que nos esté dando las 100 filas PRIMERAS (según en el orden indicado) de la tabla. El problema es que el valor de ROWNUM se está asignando justo al seleccionar la fila, y la operación ORDER BY se aplica después.

Así pues, la forma correcta de hacerlo en Oracle es intercalar una SELECT dentro de otra:

SELECT * FROM (la-consulta-original) WHERE ROWNUM <= 100

Es decir, para el caso que nos ocupa:

SELECT * FROM (SELECT * FROM TABLA ORDER BY CAMPO) WHERE ROWNUM <= 100


Ale, problema resuelto. A seguir tecleando.

--

Nota: no confundir ROWNUM con ROWID. Ambas son columnas que asigna ORACLE automáticamente, pero no significan lo mismo.

Si queréis una explicación muy buena y bastante detallada acerca de la diferencia entre ROWNUM y ROWID y cómo utilizar ROWNUM para consultas TOP-N y para resultados paginados, echadle un vistazo a este artículo

http://www.epidataconsulting.com/tikiwiki/tiki-read_article.php?articleId=63

martes, 19 de julio de 2016

Codificación de caracteres (charset) en una BD Oracle

Necesitaba saber cuál es el CHARSET que se está utilizando en una BD Oracle. Con la sentencia

SELECT * FROM NLS_DATABASE_PARAMETERS;

podremos saber no sólo la codificación, sino otros parámetros interesantes como el idioma, los separadores decimales y de miles, el formato de fecha y hora, la versión de la BD, el símbolo de moneda y algunas cosas más...


jueves, 23 de junio de 2016

Sesión de galletitas y galletitas de sesiones

Ayer me surgió un problemilla relacionado con sesiones y cookies, y algún pequeño misterio (aún) sin resolver. A ver si consigo explicar un poco el tema de las cookies y las sesiones para mi yo del futuro, y a ver también si alguien me puede dar una pista acerca del misterio.

1. Sesiones

Una sesión es una forma de mantener el estado utilizando un protocolo como HTTP que, por construcción, no está pensado para mantener el estado. Normalmente, una sesión dura desde que el usuario abre el navegador hasta que lo cierra. El servidor web puede pasar información entre diferentes páginas durante la misma sesión. Cuando el navegador solicita la página (esa que va a trabajar con sesiones), el servidor web le asigna un identificador de sesión (SESSID), se lo envía al cliente y le pide que lo guarde "en algún sitio" (normalmente, en una cookie). Después, cada vez que el usuario haga una petición de página, el servidor web recibirá ese SESSID en una cookie (eso es lo habitual, aunque también puede enviarse en la propia URL de acceso). Conservando cierta información ligada al identificador de la sesión (en el servidor web), puede darse un servicio personalizado al usuario, como mantener un carrito de la compra, por ejemplo. Para mantener esta información, el desarrollador web puede almacenar determinadas variables (nombre:valor) en la sesión.

El servidor web guardará la información de la sesión en algún sitio, puede ser en ficheros de texto, binarios, en una base de datos... Normalmente, una configuración por defecto de Apache + PHP implica que se guarden en ficheros de texto plano.

Vale. Hasta aquí bien.

2. Cookies

Para guardar en el navegador del cliente el ID de sesión se utilizan cookies habitualmente. El servidor web le pide al navegador del usuario: "Anda, porfa, ¿puedes guardar por ahí en algún sitio que la variable <loquesea> almacene el valor <patatín-patatán>? Más adelante, cuando vuelvas, yo te pediré si tienes esta información guardada y, si la tienes, te reconoceré y sabré que eres tú, mi amor" (sí, los servidores web son así de ligones con los navegadores).

Los servidores pueden usar así esta información para "recordar" las preferencias del usuario, su login automático y un sinfín de cosas más útiles y beneficiosas para el usuario, y algunas otras que no son tan beneficiosas para el usuario (pero sí para ciertos sitios web, especialmente si venden publicidad o si tienen intenciones algo más siniestras), por ejemplo, para rastrear las preferencias y hábitos de navegación.

Los navegadores o, lo que es lo mismo, los usuarios, sabemos que si nos hacemos los estrechos la página no nos va a dar mucha funcionalidad, así que nos ponemos en modo facilón, nos dejamos querer y accedemos fácilmente a que el servidor web nos penetr meta esos datos sin excesivos reparos. (¿Cuántas veces has "Entendido" que un sitio web utiliza cookies y has consentido? Luego no vayamos con excusas, que así pasa lo que pasa).

El navegador guarda ese paquetito de información, esa cookie, en un fichero pequeño normalmente de texto (también pueden ser binarios) o, algo que se ve cada vez más en las últimas versiones de los navegadores, en bases de datos locales tipo SQLite o similares. En cualquier caso, ficheros en el ordenador del cliente web. O, al menos esto es así en teoría, ya que aquí me surge el primer misterio que mencionaba en la introducción. Pero antes de abordar el misterio, recapitulemos lo que llevamos dicho hasta ahora.


Resumiendo:

- Cookies: pequeños trozos de información que almacena el navegador en ficheros de texto, binarios o BD locales tipo SQLite. Se pueden usar para muchas cosas, una de ellas para almacenar ID de sesión (pero también para rastrear al usuario, conocer preferencias...)

- Sesiones: interacciones entre navegador y servidor web que recuerdan info (estado) del usuario, normalmente usando cookies. La información se almacena en el servidor, ya sea en ficheros de texto (pares nombre:valor), binarios, bases de datos...

3. Vale. ¿Y dónde se almacenan?

3.1 LAS COOKIES

Pues... depende del navegador. En el caso de Firefox, el directorio es el del perfil del usuario, como explican aquí

https://support.mozilla.org/es/kb/perfiles-el-lugar-donde-firefox-almacena-tus-contr

que se encuentra dentro de la carpeta %AppData%\Roaming\Mozilla\Firefox\Profiles

Si sólo tienes un perfil de usuario, éste incluye la palabra 'default' en el nombre de la carpeta. En mi caso:

%AppData%\Roaming\Mozilla\Firefox\Profiles\67bl3kpn.default-1443683694326

y, dentro de esta carpeta, hay un archivo cookies.sqlite


Fig. 1. Archivo SQLite de cookies en Firefox

El contenido de este archivo se puede ver con cualquier visualizador de ficheros SQLite. A mí me gusta mucho el SQLite Expert (edición Personal). Más adelante le echaremos un vistazo.

En el caso de Chrome, la ruta donde se almacena la BD de cookies es

%LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies

y el fichero se llama cookies a secas, sin extensión. También lo podemos abrir con SQLite Expert o cualquier otro visor.


Si echamos un vistazo a ambos ficheros, veremos cosas parecidas a esto en el caso de Firefox


Fig.2. Fichero de cookies en Firefox 47.0

y esto en el caso de Chrome



Fig.3. Fichero de cookies en Chrome

3.2 LAS SESIONES

La información de la sesión se almacena, como hemos dicho, en el servidor web. En el caso de Apache con PHP, que es el servidor que yo voy a utilizar en este caso, la ubicación de las variables de sesión se almacenan en el directorio que se especifique en php.ini, en el parámetro session.save_path. En mi caso, utilizando un paquete XAMPP integrado, la ruta es C:\xampp\tmp. 

 Fig.4. Configuración de la ruta donde guardar las sesiones

Si miramos en esa carpeta, veremos las sesiones que se han ido creando en los últimos días:




Fig.5. Contenido de la carpeta de almacenamiento de sesiones

4. ¿Me puedes poner un ejemplo?

Sí, será lo mejor. Todo esto puede ser un poco confuso, y la mejor forma de verlo claro es con un ejemplo. Haremos dos páginas PHP que se pasarán información de una a otra utilizando una sesión de usuario, e iremos viendo dónde, cómo y con qué contenido se van creando los ficheros.

Página 1


01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones - Página 1</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />'
15 . 'Nombre: ' . session_name() . '<br />';
16
17 //almacenamos algún dato en esa sesión
18 $_SESSION['mes'] = 'junio';
19 $_SESSION['usuario'] = 'pepe';
20 $_SESSION['flor'] = 'amapola';
21
22 echo '<pre>';
23 print_r($_SESSION);
24 echo '</pre>';
25 echo "<br>Ir a <a href='pagina2.php'>la página 2</a> a consultar las variables de sesión"
26
27 ?>
28
29 </body>
30 </html>



Y esta es la página 2


01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />Nombre: ' . session_name() . '<br />';
15
16 //recuperamos los valores almacenados en la sesión
17 echo 'Recuperamos el valor mes=' . $_SESSION['mes'] . '<br />';
18 echo 'Recuperamos el valor usuario=' . $_SESSION['usuario'] . '<br />';
19 echo 'Recuperamos el valor flor=' . $_SESSION['flor'] . '<br />';
20
21 echo '<pre>';
22 print_r($_SESSION);
23 echo '</pre>';
24 ?>
25 </body>
26 </html>



Abramos en el navegador la página 1

 Fig. 6. Página 1, creando la sesión y almacenando algunas variables en ella.

Antes de pasar a la página 2, veamos qué ha pasado en los sistemas de archivos del servidor y del cliente.

En el servidor

Fig. 7. Fichero de sesión en el sistema de archivos del servidor web

como se ve, el nombre del archivo coincide con el session_id que hemos visto en la página. Si abrimos ese fichero, su contenido es:


Fig. 8. Contenido del fichero de sesión con las variables de la misma

Hmmm... Interesante. Vemos que junto a cada variable aparece un tipo de datos (s=string), su tamaño (5) y su valor ("junio"). Las distintas variables de sesión se separan con pipelines.


Pero es a la hora de ver qué ha pasado con las cookies en el cliente cuando aparece el primer "misterio". En teoría, las cookies se almacenan en la BD SQLite que hemos dicho. Pero, cuando muestro las cookies ordenadas por fecha de acceso, no encuentro la cookie de nombre PHPSESSID, que es el nombre que me devuelve la función session_name(). Esto es lo que consigo ver cuando accedo a la BD y filtro por el dominio 'localhost':

Fig. 9. Contenido del fichero de cookies para el dominio 'localhost' en Firefox. 
¿Y la cookie PHPSESSID?

Como tras muchos intentos no he conseguido encontrar esta información en la BD, al final he instalado el plugin Firebug en Firefox, que permite visualizar las cookies y ahí sí que puedo ver la cookie que me interesa, como se puede ver un poco más arriba en la figura 6. También si abro la página 2 con el Firebug activado (F12) puedo ver que la cookie de sesión PHPSESSID es la misma

Fig. 10. Página 2 mostrando la cookie de sesión y las variables asociadas, junto a sus valores

Aún no he conseguido averiguar dónde se almacena la cookie PHPSESSID, pero es obvio que el navegador la está guardando en algún sitio y a mí me gustaría saber dónde. Vale que con el Firebug puedo inspeccionar la cookie, lo cual me soluciona la papeleta por el momento, pero me quedo un poco mosca con no haber podido encontrar la ubicación exacta de la cookie en el disco.

¿Alguien tiene alguna idea?

martes, 10 de mayo de 2016

Estilos CSS alternativos

A veces nos puede interesar ofrecer dos páginas de estilos CSS diferentes para una página web. Por ejemplo, una versión normal y otra aumentando el contraste, para facilitar la accesibilidad de personas con problemas visuales. O simplemente por ofrecer dos temas diferentes al usuario, basados en gustos por determinados colores o fuentes, disposición de elementos en la página...

Veamos cómo hacer esto.

Definiremos dos hojas, estilo1.css y estilo2.css con los siguientes contenidos:

Fichero 1
/* estilo 1, normal */
body {
    font-family: verdana,helvetica;
    color: #0000FF; /* texto azul */
    background-color: #f8e8a0; /* fondo amarillo */
    font-size: 16px;
}


Fichero 2
/* estilo 2, alto contraste, letras grandes */
body {
    font-family: verdana,helvetica;
    color: #ffffff; /* texto blanco */
    background-color: #000000; /* fondo negro */
    font-size: 36px;
}


Y ahora hacemos nuestra página, tal que así

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>sin título</title>
<link href="estilo1.css" rel="stylesheet" type="text/css" title="Estilo 1">
<link href="estilo2.css" rel="stylesheet" type="text/css" title="Estilo 2">
</head>

<body>

<h1>Elija una de las siguientes opciones</h1>
<ol>
    <li>Opción primera</li>
    <li>Opción segunda</li>
    <li>Opción tercera</li>
</ol>
</body>

</html>


Si abrimos ahora la página en el navegador, la veremos con el primer estilo aplicado:



Fig 1. Página mostrada con el Estilo 1

Y en el menú Ver / Estilos podemos elegir cambiar a otro estilo

Fig 2. Elegir un estilo diferente en el navegador


Y veríamos el resultado que hemos definido de alto contraste y letras grandes, tal que así:

Fig 3. Página mostrada con el Estilo 2

ALGUNAS EXPLICACIONES

En los atributos "title" hemos puesto los valores "Estilo 1" y "Estilo 2". Si no hubiéramos definido el atributo "title" en alguno de los estilos esto significaría que el estilo es permanente. Esto significa que se aplica siempre, independientemente de otros estilos que se eligieran por el usuario. Si hay enlaces a varias páginas CSS sin "title", se aplicarían todas ellas.

Al incluir el atributo "title", lo que estamos haciendo es indicar que esos estilos son preferidos. Esto significa que se aplican por defecto (en nuestro caso, el "Estilo 1"), pero si el usuario selecciona otra(s) página(s) con título diferente, dejarían de aplicarse las primeras y pasarían a aplicarse todas las que tuvieran el otro estilo (en nuestro caso, el "Estilo 2"). Si dos o más páginas tienen el mismo título, entonces se aplican todas ellas al elegir el estilo correspondiente en el menú Ver del navegador.

Normalmente, hasta aquí suele ser suficiente. Pero el estándar HTML también define otro valor posible para el atributo "rel"

rel="alternate stylesheet"

Aunque no todos los navegadores implementan esto. Básicamente, está indicando que hay otros estilos alternativos, pero, al contrario que los estilos preferidos (que se aplican por defecto, al menos el primero de ellos), los estilos alternativos no se aplican por defecto, sino sólo en el caso de que el usuario lo seleccione explícitamente.

La verdad es que con utilizar estilos con títulos ya tendríamos un sistema de estilos alternativos, así que en general esta etiqueta me parece innecesaria para solucionar situaciones como la planteada al principio. Seguramente pueda idearse algún caso donde la diferencia sea significativa, pero en general no lo considero algo crítico, al menos para casos típicos.

Si queréis unas explicaciones más detalladas, echadle un vistazo a esta página, que está muy bien explicado todo esto.
Related Posts Plugin for WordPress, Blogger...