Friday 10 April 2009

Redirección interna con RewriteRule, RewriteCond y paso de variables PHP

Llevo 5 horas leyendo, buscando, probando... y por fin hallé lo que necesitaba!!!!!!!! Lo más frustante ha sido encontrar apenas casos como el mío... lo cuál no acabo de entender pues creo que es de lo más normal.

Mi caso es el siguiente: tengo un site funcionando en inglés

http://midominio.com

y como es un CMS hecho en PHP tengo URLS del tipo:

http://midominio.com/index.php?pag=foro

Lo que necesito es convertir mi CMS en multi-idiomas con los menos cambios posibles y de una forma amigable para usuarios y buscadores. ¿Creo que es algo muy generalizado, verdad? Por eso no entiendo el porque me ha costado encontrar referencias que me ayudaran. Tal vez he estado buscando mal ;)

La solución que buscaba era la de poder tener URLS como

http://midominio.com/es/index.php?pag=foro

que llevara "internamente" (es decir, sin que el visitante se dé cuenta... es decir, sin que camb ie la URL en su navegador) a esta otra URL

http://midominio.com/index.php?pag=foro&idioma=es

De esta forma el visitante podría estar navegando siempre por el "subdominio" /es/ pero en realidad la aplicación de PHP que es llamada es siempre la misma solo que se le pasa la variable idioma y de esta forma puede mostrar los contenidos adecuados! ;)

La última condición que necesitaba cumplir es que el archivo PHP llamado fuera cualquiera y no solo index.php, y que las variables de la URL fueran cualesquiera y cuantas sean necesarias.

Y en fin, después de 5 horas de lecturas y pruebas hallé el contenido del archivo .htaccess que tengo que poner en la raiz de ese dominio:

Options +FollowSymLinks
RewriteEngine On
RewriteRule ^es/$ $1?idioma=es
RewriteRule ^es$ $1?idioma=es
RewriteCond %{QUERY_STRING} ^([^/]+)
RewriteRule ^es/(.+)\.php$ $1.php?%1&idioma=es
RewriteRule ^es/(.+)$ $1

No quiero hacer un tutorial aquí del tema, porque sería larguísimo y además no tengo los conocimientos adecuados. Posiblemente incluso la sintaxis que he utilizado podría mejorarse. Pero voy a detallar lo poco que he entendido de estas líneas de código y su función:
  • las dos primeras líneas son necesarias sí o sí si se quieren utilizar en APACHE estas reglas de redirección
  • la 3a y 4a líneas son para añadir "?idioma=es" a la URL cuando no menciona ningún archivo PHP: http://midominio.com/es
  • las líneas 5a y 6a van "ligadas". La primera "lee" el QUERY_STRING que son las variables $_GET que vienen después del "archivo.php?", y las insertamos en el output de la línea 6a como %1.
  • la línea 7 es para que se redireccionen todas las demás URLS, esta vez sin añadir nada del idioma, como las imágenes o las hojas de estilo CSS.
Para el que quiera saber más, decir que las sentencias RewriteRule manejan siempre la sintaxis:

RewriteRule input output

En donde input es una "expresión regular" (expresión que usa un lenguaje de comodines) que coincidirá solamente con algunas URLs concretas, y output es simplemente las URLS que finalmente debe construir y llamar Apache, utilizando normalmente "partes" del input. Esas partes son $1,$2,$3... (tal como se utilizan en la sintaxis del output), y se corresponden con el contenido de los diferentes paréntesis () que hay en la expresión regular del input.

Ejemplo:

RewriteRule ^es/(.+)\.php$ $1.php?%1&idioma=es

coincidirá con URLS del tipo:

es/index.php?pag=foro (pag=foro sería el QUERY_STRING,%1)
y será transformada en index.php?pag=foro&idioma=es
Como véis, el nombre del archivo php (index) es una variable! ;)
y el QUERY_STRING también aunque se lee en una línea RewriteCond previa.

La verdad es que el tema es un auténtico lío, jejeje... así que no dudo de que alguno de vosotros no haya entendido la mitad! Lo que si puedo aseguraros es que si queréis entender algo deberíais empezar por leer algún tutorial sobre el tema de .htaccess o de las expresiones regulares, que es de lo que va esto ;)




Actualización 6 Enero 2010

Podemos simplificar aún más el trabajo con idiomas

Trabajando esta semana en el tema de los idiomas (de nuevo) he descubierto que se puede conseguir lo mismo que pretendíamos al inicio de este artículo pero de manera un poco más "limpia" y cómoda. Me explico.

Arriba hemos utilizado el archivo .htaccess para realizar una redirección interna de:

http://midominio.com/es/index.php?pag=foro

a

http://midominio.com/index.php?pag=foro&idioma=es


Y sin embargo he descubierto que podemos optar a una redirección más sencilla (con menos líneas y menos complicación en el archivo .htaccess) si usamos bien el PHP ;) En concreto, la redirección la llevaremos simplemente hacia:

http://midominio.com/index.php?pag=foro

Es decir, que literalmente nos "comemos" el "es/" (más abajo pongo como ha de ser el .htaccess), y usamos el PHP para "leer" el "idioma" (en nuestro ejemplo 'es') en la URL (a la que accedemos con $_SERVER['REQUEST_URI']). El código PHP sería algo como esto:

$a_idiomas=array('es','en','fr');
$REQUEST_URI=$_SERVER['REQUEST_URI'];
foreach($a_idiomas as $v){
    if ( !strpos($REQUEST_URI,'/'.$v.'/')===false
       or substr($REQUEST_URI,-1*(strlen($v)+1))=='/'.$v )
          $idioma=$v;
}


En el array $a_idiomas cargamos las siglas de los idiomas que usamos en nuestro site. En $REQUEST_URI cargamos la URL solicitada (sin incluir el dominio o host), es decir, en nuestro ejemplo sería /es/index.php?pag=foro. Después, solamente ejecutamos un bucle que recorre cada uno de los idiomas buscando si sus siglas aparecen como /es/ o como /es (en una URL del tipo http://midominio.com/es). Si ocurre alguno de estos casos, entonces ya hemos encontrado cuál es el idioma que el visitante está requiriendo ;)

Fijaros que no hemos necesitado que la redirección interna nos lleve a una URL que contenga la variable GET &idioma=es. Y eso se va a traducir en lo siguiente: un archivo .htaccess mucho más limpio:

Options +FollowSymLinks
RewriteEngine On

RewriteRule ^es$ $1
RewriteRule ^es/$ $1
RewriteRule ^es/(.+)$ $1


Es decir, de 5 reglas nos hemos quedado con solo 3, y además las más sencillas! No entiendo muy bien la razón pero no he podido deshacerme de una de ellas. Intuyo que en realidad las 2 últimas RewriteRule son redundantes, pero después de hacer pruebas he comprobado que ambas son necesarias :S

Pero no se vayan... aún hay más! jejeje

Cuando no trabajamos en la raíz del dominio

Uno de los comentaristas (Carlos) preguntó acerca de trabajar en "local", en dónde lo anterior sufre algunos problemillas... :S En mi caso, también tuve problemas cuando quise trabajar en un directorio por debajo de la raíz del dominio, como por ejemplo en:

http://midominio.com/sports/es/index.php

Para no extenderme mucho, la solución que me ha funcionado ha sido usar la cláusula RewriteBase /directorio que indica al servidor Apache que si usamos redirecciones absolutas queremos que la "raíz" no sea la del dominio (http://midominio.com/), sino http://midominio.com/directorio. Esta cláusula nos será útil y necesaria solo si usamos el parámetro [R] al final de algún RewriteRule. Este parámetro obliga al servidor a CAMBIAR la URL que vino del navegador por la resultante de aplicar la regla Rewrite !!!

Para aclararnos, veamos como yo he dejado mi archivo .htaccess que he colocado en /sports:

Options +FollowSymLinks
RewriteEngine On

RewriteBase /sports

RewriteRule ^es$ es/ [R]
RewriteRule ^es/$ $1
RewriteRule ^es/(.+)$ $1


Añadir a lo dicho, que ahora la primera regla ya no es una redirección INTERNA, sino EXTERNA, es decir, al llegar a ella, si la URL pedida por el visitante cumple con la expresión ^es$ entonces se va a modificar la URL del navegador del visitante por una URL exactamente igual pero acabada en 'slash' /.

¿Para qué complicarnos tanto la vida? Pues porque de esta forma si alguien escribe:

http://midominio.com/sports/es

será redirigido a:

http://midominio.com/sports/es/

y con esto los enlaces relativos que hayan en esa página serán considerados por el navegador como relativos al directorio /es/ !!!! de otra forma serían considerados como relativos al directorio /sports/.

Bueno, como pueden ver... este tema es más complejo de lo que aparenta. Y me da la sensación de que hay muchos casos diferentes, y que han de buscar en su caso qué es lo que funciona. Les recomiendo que prueben y prueben... el "ensayo y error" también sirve ;)

Aquí tienen dos enalces por si necesitan leer más, del sitio oficial de Apache:



Una última aclaración: mod_rewrite es el módulo de Apache que usa el .htaccess, y está activado por defecto diría que en casi cualquier servidor de hosting Linux+Apache.

Suerte!
SERGI

18 comments:

  1. creo que has dado en el clavo, yo estaba buscando sobre las variables get para hacer tipos de redirección más complejos del tipo
    "pag/foro/idioma/es", lo cual según he entendido acabaría siendo algo así:

    "pag/%1/idioma/%2"

    ReplyDelete
  2. No Chema, creo que te equivocas.
    Se utiliza %1 para trozos del URL definidos con

    RewriteCond %{QUERY_STRING} ^([^/]+)

    que se corresponden con las variables GET (las que en la URL aparecen después de ".php?"

    Si lo que quieres es tomar trozos de la URL que están antes de las variables GET (como nombres de directorios: /foro/ o /es), entonces debes hacer algo como esto:

    RewriteCond %{QUERY_STRING} ^([^/]+)
    RewriteRule ^pag/(.+)/idioma/(.+)/(.+)\.php$ $3.php?modulo=$1&idioma=$2&%1

    de esta forma si el visitante visita:

    pag/foro/idioma/es/index.php?pag=2

    el sistema lo interpretará como:

    index.php?modulo=foro&idioma=es&pag=2

    De todas formas, pruébalo. Pues tal como dije en el artículo, son mis primeros pasos con esta gramática, y no puedo asegurarte que no me haya dejado algún símbolo!!! jejeje... pero creo que voy bien encaminado.

    SERGI

    ReplyDelete
  3. Yo tengo otro problema y creo que lo mio es mas simple, tengo un hosting limitado en espacio, pero mis trabajos los tengo en otro servidor que es mas espacioso, lo que necesito es poder "redireccionar" al cliente a la maquina con los trabajos sin tener que darle la url de destino, me explico
    al cliente 1 le doy esta url
    http://midominio.com/trabajos/cliente1

    al cliente 2 le doy esta otra
    http://midominio.com/trabajos/cliente2

    las carpetas cliente1 y cliente2 no existen, pero son redireccionadas a
    http://ip-maquina-interna/~cuenta/cliente1

    y cliente2 respectivamente

    el htaccess donde lo meto y como creo la regla?

    ReplyDelete
  4. Hola pues no, no eres el único que se pasa horas con el tema este...está bien saberlo, mucha inforamación pero poca resolución parece.

    Una consulta, en el caso de los idiomas, para acceder a ellos desde un enlace (ya que normalmente habrá más de un idioma) imagino que se debe poner ya la URL amigable (otro tema que no encuentro información clara), pero si en el enlace le pongo esto <a href="es/index.php"> meduplica el directorio
    http://localhost/misitio/es/es/index.php

    Si tienes alguna idea te lo agradecería
    gracias anticipadas
    carles

    ReplyDelete
  5. @carles carrera:

    sí, lo que tú preguntas tal vez es algo que no expliqué. Tienes que utilizar una barra '/' al inicio de las direcciones!

    '/es/index.php'

    (en lugar de 'es/index.php')

    Porque lo que necesitamos es que las URLs se "referencien" respecto la raíz de nuestro dominio, y que no nos pase lo que te sucede a ti ;)

    Me explico un poco más (por si me lee alguien con menos experiencia en este tema). Creo que con un ejemplo quedará todo aclarado. Supongamos que estás montando una página en el subdominio:

    http://midominio.com/sports

    si en un enlace escribimos:

    href='es/index.php'

    el visitante será dirigido a esta página al hacer clic:

    http://midominio.com/sports/es/index.php

    porque al no iniciar con '/' el href, el navegador interpreta que esa dirección relativa debe ir a continuación de la ubicación de la página actual, en este caso, 'sports/'.

    Sin embargo si construimos el enlace con:

    href='/es/index.php'

    entonces nos llevará a la página:

    http://midominio.com/es/index.php

    porque la barra '/' al inicio del href indica que la URL del enlace es "relativa al dominio", es decir, hasta el '.com'.

    En realidad parece sencillo, y lo es. Pero cuando uno no lo ha leido nunca... puede volverse loco, no?! jajaja... espero haber sido de ayuda.

    Por cierto, lo mismo sería válido para las URLs de las imágenes que incluyamos en nuestra página. Es decir la propiedad 'src' de las mismas. Lo digo porque cuando empezé a trabajar con este tema de los idiomas con URLs amigables me sucedió lo mismo que a ti! es decir, para evitar errores, utiliza URLS con la barra '/' delante, también para las imágenes!

    Saludos,
    SERGI

    ReplyDelete
  6. Hola pues gracias cuando he conseguido entender el orden natural de este lio mas o menos ya entiendo como funciona...de una forma básica
    Lo malo que trabajar en local con directorios /es/ con la barra en el principio no funciona bien, porque me saca los directorios de la carpeta del proyecto a la raiz de localhost
    http://loclahost/porject/es/productos
    http://localhost/es/productos.
    ...esto igual con virtual host se soluciona ya probaré de momento ya me sirve

    Otra cosa es generalizar para tres opciones para no generar muchas lineas de código, por ejemplo
    RewriteRule ^en/products$ productos.php?lang=en
    RewriteRule ^ca/productes$ productos.php?lang=ca
    RewriteRule ^es/productos$ productos.php?lang=es

    estas tres lineas tendrían que ser solo una como esta no?, pero no me acaba de funcionar, carga la página pero no las hojas de estilo
    RewriteRule ^([a-z]{2})/(.*)$ productos.php?lang=$1&cat=$2

    he visto que en re direcciones como estas añaden al final [R] o [R,L]
    he probado de ponerlo también a la redirección de arriba pero no ha funcionado, agluna idea del error?

    gracias de nuevo, me ha ayudado mucho tu post paar entender un poco el lio este y creo que mucho más sencillo que muchos ejemplos que he visto por ahí.

    ReplyDelete
  7. RewriteRule ^([a-z]{2})/(.*)$ productos.php?lang=$1&cat=$2

    por cierto esta redirección vendría a ser
    en/products
    ca/productes
    es/productos
    que es lo que hay en los enlaces

    ReplyDelete
  8. A ver, respondo casi un mes más tarde, pero creo que hoy encontré la respuesta al problema que expones de "trabajar en local" y a la vez usar direcciones absolutas. La clave está en usar el comando:

    RewriteBase /directorio/project

    juntamente con el [R] que comentas, que sirve para convertir una redirección interna en ABSOLUTA!! es decir, altera la URL original escrita en el navegador añadiéndole http://..../directorio/project

    Como es complejo de explicar, he preferido hacerlo modificando el artículo original ;)

    Además, he descubierto que es posible trabajar Apache+PHP de una forma un poco más sencilla el mismo tema de las URL amigables.

    Un saludo,
    SERGI

    PD: me alegro de leer que los ejemplos que he ido poniendo en este artículo sirven a otros usuarios... gracias por el feedback ;)

    ReplyDelete
  9. Muchas gracias por los ejemplos, tenia problemas tomando los GETs.

    La unica duda que me quedo es cual es el objetivo de ^([^/]+) en %{QUERY_STRING}, no seria lo mismo buscar (.+)?

    ReplyDelete
  10. Hola Uiman... me alegro que te hayan servidor los ejemplos. Tal como dije, es increíble que cueste tanto de encontrar literatura clara sobre este tema :S

    Sobre lo que me preguntas... no tengo ni idea. A ver si alguien que pase por aquí lo responde. De todas formas, siempre puedes probarlo... porque si funciona se ve inmediatamente ;)

    Saludos!
    SERGI

    ReplyDelete
  11. Pues... me gustaría preguntar si se puede lograr algo parecido a facebook.com/perfil
    me gustaría muchisimo saber como confurar eso

    ReplyDelete
  12. Usher, amigo, el tema de .htaccess es enorme y tienes razón hay poca información, yo actualmente uso este .htaccess para mis proyectos php en MVC:

    Options -Indexes
    ErrorDocument 403 403.php
    ErrorDocument 404 404.php

    Options -Multiviews
    RewriteEngine On
    RewriteBase /
    RewriteRule ^configs/(.*)$ - [L]
    RewriteRule ^includes/(.*)$ - [L]
    RewriteRule ^data/(.*)$ - [L]
    RewriteRule ^models/(.*)$ - [L]
    RewriteRule ^views/(.*)$ - [L]
    RewriteRule ^controllers/(.*)$ - [L]
    RewriteRule ^([0-9a-z_-]+)/?$ index.php?controlador=$1 [L,QSA]
    RewriteRule ^([0-9a-z_-]+)/([^/]+)/?$ index.php?controlador=$1&action=$2 [L,QSA]
    RewriteRule ^([0-9a-z_-]+)/([^/]+)/([^/]+)/?$ index.php?controlador=$1&action=$2&namex=$3 [L,QSA]

    AddCharset UTF-8 .php

    ReplyDelete
  13. Hola:
    Bueno aunq veo q es un tema un poco antiguo, no se si me pueden ayudar en lo siguiente
    quiero q al acceder a una carpeta con muchos archivos(doc,pdf,jpg,etc), antes se redireccione a un script php para hacer ciertas validaciones.
    ¿Cómo se haría?

    ReplyDelete
  14. Hola anónimo, en google puedes encontrar ayuda al respecto, como por ejemplo:

    http://www.google.com.mx/search?q=rewriterule+para+extensiones+de+archivo

    o haz búsquedas parecidas. La verdad... nunca he tenido que resolver el problema que tú planteas ;)

    SERGI

    ReplyDelete
  15. RewriteEngine On

    #personalizar errores de navegador
    ErrorDocument 404 /error.php
    ErrorDocument 403 /error.php


    #los archivos .aspx me los leerá como .php
    AddType application/x-httpd-php .cesarcancino




    RewriteRule ^.*/([0-9]+)\/$ anuncios.php?id_anuncio=$1 [S]

    RewriteRule ^.*c-([0-9]+)\/$ index.php?cat=$1 [L]


    htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
    tengo un htaccess
    y me produce este error
    me pueden ayudar

    ReplyDelete
  16. Te funciona algún .htaccess en ese servidor? es decir, parece un error debido a que te falta activar el modulo de apache mod_rewrite, que si no me equivoco es el que se encarga de interpretar los archivos .htaccess

    http://httpd.apache.org/docs/current/mod/mod_rewrite.html

    si estás en un hosting compartido tendrás que pedir que te lo activen... es raro que no esté activado, la verdad...

    ReplyDelete
  17. gracias ya lo corregi son prueba que realizo en mi computadora

    ReplyDelete
  18. Buenas,
    Gracias por la info, pero creo que no se aplicarla correctamente.
    Tengo un pequeño problema con unas redirecciones del que no consigo salir :S

    Tengo lo siguiente en el mismo virtualhost:
    ServerName des-principal.local
    ServerAlias des-principal.es

    Si pongo la url sin uri si que me hace la redirección, pero el problema es cuando añado el uno a la url:
    http://des-principal.es/uno quiero que redirija a http://des-principal.local/uno
    pero se queda en la misma url: http://des-principal.es/uno

    He probado montones de rewrite con reglas, condiciones y no consigo hacerlo con ninguno.
    ¿Podéis echarme una mano?

    Gracias!

    ReplyDelete