Carlos Robles frikiblog

11/08/2011
by carlosrobles
0 comments

Un articulo en Joomla tiene un candado y no lo puedo editar

Tal y como indica, esta Checked out. Los de Joomla han querido hacer un poquito de CVS y teniendo en cuenta que puede haber varias personas editando articulos a la vez, no te dejan abrir uno que ya esta abierto. Debajo de las palabras Checked out, esta el nombre de usuario que supuestamente lo ha abierto, y la fecha en la que lo hizo.

Si has hablado con la demas gente, y nadie tiene abierto el articulo, o en realidad solo estás tu, no te preocupes, para eso está el gran desconocido menu Tools > Global Check-in. Nunca he tenido el admin en español, supongo que sea algo como Herramientas > Checkin Global. Esto marca absolutamente todo como checked in, es decir, que se puede usar normalmente.

Vamos, que si Joomla cree que alguien esta usando algo, te lo bloquea para que lo sepas, pero que si quieres desbloquearlo, tu mandas.

11/08/2011
by carlosrobles
0 comments

Añadir campo teléfono al formulario de Contacto de Joomla

Hay un millon de extensiones y trucos para cambiar el formulario de contacto de Joomla, que es muy simple. Pero si quieres hacer una cosa rapida y que funcione, estos dos pasos pueden servir.

Una de las cosas que se  echan de menos es el teléfono. Podemos añadir un campo telefono en un formulario en 1.5.22 de esta forma:

1. Vamos a components/com_contact/views/contact/tmpl/default_form.php y por ejemplo despues de

<label id="contact_emailmsg" for="contact_email">
  <?php echo JText::_( 'Email address' );?>:
 </label>

 <input id="contact_email" type="text" name="email" value="" size="30" maxlength="100" />

Añadimos:

<label id="contact_phomemsg" for="contact_phone">
  <?php echo JText::_( 'Teléfono' );?>:
 </label>

 <input id="contact_phone" type="text" name="phone" value="" size="30" maxlength="100" />

2. Despues vamos a 1. components/com_contact/controller.php y en la funcion submit, despues de

		$body		= JRequest::getVar( 'text','','post' );
		

ponemos

$phone		= JRequest::getVar( 'phone','',	'post' );

Y para no perder mucho tiempo con la logica de la aplicacion, yo propongo añadirlo directamente al body. De todas formas es un buen metodo. Algo que se puede hacer es crear todo el body con su tablita con los datos, pero como esto no deja de ser un mail interno, no necesita muchas flores, asique nos podria salvar el asunto algo simple, como directamente esto

$body.="\n\nTeléfono: $phone";

07/08/2011
by carlosrobles
0 comments

SQLite en Android, distribuir aplicacion con base de datos por defecto

Una de las formas mas sencillas y eficaces de guardar información persistente en Android es en una base de datos SQLite.
Como veremos en miles blogs, sencillamente se trata de crear una base de datos, y luego rellenarla y leerla cuando nos convenga. La clase SQLOpenHelper ayuda con toda la tarea. En internet se encuentran mil manuales.
Una duda que puede surgir inicialmente, es qué hacer cuando queremos distribuir una base de datos con datos. Por ejemplo, en el juego que estoy desarrollando, guardo una base de datos con los niveles, el estado (resuelto, bloqueado, activo), el tiempo empleado en resolverlo,  y la cantidad de hitos conseguidos. Crear la base de datos sobre la marcha es muy sencilla, porque siempre se crea vacia, y es inmediato  con las propias funciones de SQLOpenhelper. Pero qué pasa cuando queremos que la base de datos vaya con informacion incluida? por ejemplo en el caso de que nuestro programa muestre informacion offline sobre el tema que sea, y esa informacion está en BD. Pues se pueden hacer 3 cosas:

  1. Podemos tratar de crear todas las queries a mano, y meterlas en el onCreate, es decir, ademas de la estructura, que haga inserts de los datos.
  2. Crear la base de datos programaticamente, por ejemplo con una aplicacion que creamos para recoger datos de donde sea y meterlos en la base de datos SQLite, despues exportala a código (un dump de toda la vida), y copiar ese código en la funcion onCreate. Esto es una version de lo anterior.
  3. La siguiente, y un poco más limpia es: crear la base de datos que te dé la gana, y buscar el archivo que la alberga, porque SQLite, evidentemente no hace magia, y guarda un archivo para  las bases de datos.

La Opcion 3, es la menos inmediata. Y es la que es mas digna de explicar:

Buscamos ese archivo, normalmente estará en /data/data/NOMBRE.DEL.PAQUETE/databases/
Recuerda que tanto para simuladores como para dispositivos reales, podemos utiliar la perspectiva DDMS para explorar el sistema de archivos.

Cuando lo encontremos… lo copiamos y lo ponemos en la carpeta de Assets de nuestro proyecto, donde nos guste más.

Ah0ra en el programa hacemos una funcion que se encargue de mover el archivo a la posición adecuada. Con esto conseguimos que la base de datos se incluya en el apk, y asi pueda ser distribuida, y la primera vez que se ejecute en u movil, se copiará donde corresponda para poder usar SQLite de la forma habitual.

La función podria ser algo asi:

public void onCreate() {

InputStream origen = null;

try {

origen = getAssets().open(NOMBRE_DE_LA_BASE_DE_DATOS);

OutputStream destino = new FileOutputStream(RUTA_A_LA_BASE_DE_DATOS_SQLITE +  NOMBRE_DE_LA_BASE_DE_DATOS )

byte[] buffer = new byte[1024];

int length;

while ((length = origen.read(buffer)) > 0) {

destino.write(buffer, 0, length);

}

dbOut.flush();

dbOut.close();

assetsDB.close();

Log.i(TAG, "New database created...");

} catch (IOException e) {

Log.e(TAG, "Could not create new database...");

e.printStackTrace();

}

}

Esto no tiene ninguna magia, mueve el archivo de forma binaria, como hariamos para copiar cualquier otro archivo con cualquier contenido.

Para probarlo, puedes ir a la carpeta ‘Android SDK – Tools’ y utilizar la herramienta adb, asi:
run:

Abrir el shell del emulador actual:

adb -e shell

ir a la carpeta en cuestion donde deberia estar y hacer un ls, para ver si al menos estar, está.

#cd  /data/data/NOMBRE.DEL.PAQUETE/databases/

#ls

Y ahora puedes hacer alguna consulta para ver que el archivo está integro.

#sqlite3 nombre_de_la_bd
#select * from nombre_de_la_tabla;

y eso es to, eso es to, eso es todo amigos.

29/07/2011
by carlosrobles
0 comments

Problemas con las fuentes en Andengine. Null pointer exception, pantalla en blanco, etc.

Andengine es un sistema muy completo, que esta hecho en mi opinion con una arquitectura casi ideal.

A veces entender de qué va cada cosa no es lineal del todo. Hoy me he encontrado un problema: tengo una Activity que carga muchisimos recursos, y he creado un splash screen, y luego un hilo que carga todo y cuando esta listo quita el splash screen.

El sistema que uso para cargar texturas es bastante bueno, lo entiendo perfectamente, y no suelo encontrar ningun problema. Pero hoy a media carga se me ha quedado toda la pantalla blanca. Problema con las texturas seguro.

Me he puesto a seguir la carga linea a linea, y mas o menos esto pasaba al cargar un ChangeableText. Esto es basicamente un Rectangle, que es algo que tengo controlado (de esto heredan muchisimas Entities) y luego tiene una Font. Ok, primer paso: breakpoint en el constructor del ChangeableText, y cuando llego ahi, un break point en cada funcion, a ver si lo localizo mejor.

Veo que la funcion

 public synchronized void update(final GL10 pGL) 

es una gran candidata, porque es la que esta tocando el contexto de opengl.  especialmente el punto critico es:

if(lettersPendingToBeDrawnToTexture.size() &gt; 0) 

vamos, que hay algo que necesita tocar texturas, por lo tanto necesita subirse al hardware, y si no va bien, va a romper tambien lo que hubiese en la memoria grafia.

Como estamos en dos hilos, es muy probable que estemos modificando el array lettersPendingToBeDrawnToTexture en un momento que no debemos, asique buscamos donde (que evidentemente, es facil siguiendo el constructor del ChangeableText).  Sigo y sigo, y esta todo bien. Las fuentes se cargan cuando se debe, y la textura que usamos esta ya cargada mediante el loadTexture de TextureManager, (algo que en cualquier caso, sabemos que no significa que esté en hardware, y esta es la primera sospecha) volviendo  al update de Font, se nos confirma esto, el this.mTexture.getHardwareTextureID() nos devuelve un rotundo -1.
Esto es facil de entender. OpenGL en android no es tan quisquilloso con los hilos como lo es en windows, pero aun asi tiene sus normas. Podemos hacer muchas cosas en muchos hilos, pero cuando queramos subir algo al hardware, lo normal es hacerlo en el update thread, en el principal. En andengine es asi, basicamente porque es donde tenemos el objeto GL que nos ha creado el SurfaceView. Entonces, aunque hagamos el TextureManager.loadTexture(), lo unico que hacemos es marcarla para que sea subida al hardware, pero no se sube hasta que el update thread llegue al punto anterior a imprimir, que es donde comprueba las texturas que hay que subir. Claro, es facil que estemos tratando de utilizar una textura que realmente no esta en el hardware, y que no lo estará hasta dentro de un tiempo.  Hay un millon de cosas que se pueden hacer, pero por suerte con las texturas de fuente tenemos una solucion bastante sencilla.

si vamos a la funcion onDrawFrame del Engine, vemos que antes de llamar al draw de la Scene, nos esta actualizando las fuentes.

public void onDrawFrame(final GL10 pGL) throws InterruptedException {
 final State threadLocker = this.mThreadLocker;

threadLocker.waitUntilCanDraw();

this.mTextureManager.updateTextures(pGL);
 this.mFontManager.updateFonts(pGL);
 if(GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS) {
 this.mBufferObjectManager.updateBufferObjects((GL11) pGL);
 }

this.onDrawScene(pGL);

threadLocker.notifyCanUpdate();
}

Y uno se pregunta por qué no se encarga él solo de comprobar que la textura esta cargada antes de hacer nada, pero bueno, no  pidais tanto! LA solucion es facil:

Como sabemos, el proceso para utilzar una fuente es:

  1. crear la textura (new Texture),
  2. añadir la textura al TextureManager.
  3. Crear la fuente (new Font).
  4. añadir la fuente al fontManager (mEngine.getFontManager().loadFont(mFont))
  5. y luego ya se la metemos al componente que queramos,
  6. y la añadimos a la escena.

Vale, pues la solucion sale sola:

  1. crear la textura (new Texture),
  2. añadir la textura al TextureManager.
  3. Crear la fuente (new Font).
  4. esperar a que la textura este en el hardware (no pasa nada por redundar, por ejemplo:
    1. isLoadedToHardware()
    2. getHardwareTextureID()!=-1
    3. ! isUpdateOnHardwareNeeded()Todo esto lo podemos comprobar en un bucle, bloqueando, si podemos permitirnoslo, o crear un updatehandler que lo compruebe en cada iteracion, y cuando esté ok
  5. añadir la fuente al fontManager (mEngine.getFontManager().loadFont(mFont))  (si no hacemos esto, el engine no encontrará la necesidad de buscar esa textura) pero claro, tambien tenemos que tener en cuenta que tampoco podemos usarla aun, asique los demas pasos tambien deben ir detras de estos.
  6. y luego ya se la metemos al componente que queramos,
  7. y la añadimos a la escena.

He asumido que se tiene un conocimiento general bastante amplio, pero si alguien necesita mas datos sobre algo, que pregunte.

28/07/2011
by carlosrobles
0 comments

Android + Eclipse: Conversion to Dalvik format failed with error 1 – UNEXPECTED TOP-LEVEL EXCEPTION

De repente, sin haber hecho nada me encuentro on este error en mi proyecto Android en eclipse, y no puedo hacer nada. Googleo un poco, pero nada relevante. Asi que me puse a pensar, que a veces es lo más rápido.
La solucion es sencilla, algun zip esta roto, esas cosas pasan. No te calientes, busca en tu buildpath. En la ruta real, en windows, busca todos los jar que tengas añadidos. En el peor de los casos no serán muchos. Trata de descomprimirlos uno a uno, a ver si puedes. Los jar se comprimen igual que los zip, asique los puedes descomprimir de la misma manera. Si tienes el winrar o algun programa un poco completo, es facil que ya te lo reconozca. Si no, pues siempre puedes cambiar la extension de .jar a .zip, y tratar de descomprimirlo. Seguro que alguno falla, y te dice que el archivo esta roto.
Ese es el causante, logicamente, si no se puede descomprimir, imposible que eclipse puede acceder a sus archivos. Ahora, queda lo mas dificil, regenerarlo! Si tienes una copia, lo reemplazas y listo, si no, pues tendras que ir sacando por partes el contenido, hasta encontrar la carpeta o el archivo que falla, y ver si puedes conseguirlo. Si es una libreria externa, es facil que te lo puedas volver a descargar, pero si eres como yo y tienes la costumbre de modificar y recompilar cuando hace falta los frameworks que utilizas, mas te vale que tengas una copia de las fuentes!

El error completo en la consola es este:

[2011-07-28 22:37:19 - HomeDogs]: Dx
UNEXPECTED TOP-LEVEL EXCEPTION:
java.io.EOFException: Unexpected end of ZLIB input stream
[2011-07-28 22:37:19 - HomeDogs]: Dx at java.util.zip.ZipFile$1.fill(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at java.util.zip.InflaterInputStream.read(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at java.io.FilterInputStream.read(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:233)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:130)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:108)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.dx.command.dexer.Main.processOne(Main.java:284)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.dx.command.dexer.Main.processAllFiles(Main.java:220)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.dx.command.dexer.Main.run(Main.java:176)
[2011-07-28 22:37:19 - HomeDogs]: Dx at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[2011-07-28 22:37:19 - HomeDogs]: Dx at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at java.lang.reflect.Method.invoke(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.ide.eclipse.adt.internal.build.DexWrapper.run(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.ide.eclipse.adt.internal.build.BuildHelper.executeDx(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder.build(Unknown Source)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:627)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:170)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:201)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:253)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:256)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:309)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:341)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.AutoBuildJob.doBuild(AutoBuildJob.java:140)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.events.AutoBuildJob.run(AutoBuildJob.java:238)
[2011-07-28 22:37:19 - HomeDogs]: Dx at org.eclipse.core.internal.jobs.Worker.run(Worker.java:55)
[2011-07-28 22:37:19 - HomeDogs]: Dx1 error; aborting
[2011-07-28 22:37:19 - HomeDogs] Conversion to Dalvik format failed with error 1

09/06/2011
by carlosrobles
0 comments

La oferta de empleo mas geek de la historia

La gente de Simbiotica, una empresa de Madrid que se autocataloga como “Agencia creativa digital”, nos demuestra que si que son creativos, con esta oferta de empleo que ademas de divertida, sirve como primer filtro facilio para los candidatos. No puedo evitar compartirlo!

define('LATITUDE',40.4222691);
define('LONGITUDE',-3.699632);
define('HOST_URL', 'http://simbiotica.es');
define('AVAILABILITY','<<Insert your avaiability here>>');
define('MIN_EXPERIENCE',86400*30*12);
$langs = array('es', 'en');
define('LANGS', serialize($langs));
define('DEBUG_TIME',86400*30*3);
define('RUN_TIME',86400*30*6-DEBUG_TIME);
set_time_limit(0);  

/**
 * OfertaEmpleo
 *
 * @package Simbiotica
 */
class Oferta
{  

    private $interested=<<INSERT TRUE OR FALSE HERE >>;  

    private function loadResources(){
        //needed to run.
        require 'php.inc';
        require 'sql.inc';
        //highly recommended.
        include 'webServices.inc';
        include 'oop.inc';
        include 'javaScript.inc';
        include 'jQuery.inc';
        //not needed, but would improve performance.
        include 'gmapsAPI.inc';
        include 'facebookAPI.inc';
        include 'serverConfig.inc';
        include 'as3.inc';
    }  

    public function daylyWork(){
        backendProgramming();
        backendIntegration();
        dataModeling();
        databaseMaintenance();
    }  

    private function checkAvailability(){
        switch (AVAILABILITY){  

            case 'part time':
            return true;
            break;  

            case 'full time':
            return true;
            break;  

            case 'work from home':
            return false;
            break;  

        }
    }  

    private function calculateSalary($experience, $work_hours, $responsibility){
        //human interaction needed here
        //No reliable algorithm
    }  

    private function applyForOffer(){
        if($this->interested && checkAvailability()){
            $toEmail= 'info@simbiotica.es';
            $subject = 'Oferta de empleo';
            $html = '<html>
                            <head>
                            </head>
                            <body>  

                            << Insert your online portfolio & comments here >>  

                            </body>
                        </html>';
            $headers = "From: YOUR NAME HERE <YOUR E-MAIL HERE>\r\n";
            $headers .= "MIME-Version: 1.0\r\n";
            $headers .= "Content-type: text/html; charset=utf-8\r\n";  

            mail($toEmail, $subject, $html, $headers);
        }
    }  

    function __construct() {
       $this->loadResources();
       $this->applyForOffer();
   }  

}  

09/06/2011
by carlosrobles
0 comments

Despistados y principiantes: comparar Strings en Java.

Estoy depurando el juego en android en el que estoy inmerso, y me he encontrado un comportamiento inesperado. He echado un vistazo rápido al código y rapidamente me he dado cuenta, y me ha entrado un sentimiento meláncolico: se me ha escapado un fallo de principiante.

El fallo en cuestion ha sido comparar dos strings con un ==, y os cuento formas de comparar cadenas:

  1. Comparar con ==
    En java, todo son objetos, y hay que pensar asi, pero siempre. 

    Por ejemplo algo como

    String micadena= new String ("texto");
    if (micadena == "texto")
    return true;
    return false;

    devolvera false, porque las dos cadenas no son el mismo objeto, y con == es lo unico que miramos.

    curiosamente algo como

    String micadena= "texto";
    String micadena2= "texto";
    if (micadena ==  micadena2)
    return true;
    return false;

    devolverá true, porque como no hemos usado ningun new, el compilador cuando va a crear las variables, internamente hace uno, y como ve que son iguales, guarda las dos en el mismo objeto, por lo que (y sin profundizar mas en cosas futuribles) las dos cadenas son la misma.

  2. Comparar con equals()
    Esta es la primera cosa en la que hay que pensarPara comparar realmente las cadenas y no si son o no la misma instancia, se usa el método equals(). 

    String micadena = new String("texto");
    String micadena2 = new String("texto");
    if (micadena.equals(micadena2))
    return true;

    que devuelve true. Esto es lo que tenemos que usar por norma general para comparar Strings.

    La gracia, ademas, es que podemos hacerlo directamente sombre un literal, sin tener que crear un objeto.

    if ("texto".equals(micadena)) 

    Que va genial cuando no sabemos si micadena puede ser null, asi nos evitamos comprobarlo (esto no es php, si la cadena es null la vamos a liar)

  3. Comparar con compareTo()
    Esto nos dice ademas, que cadena va antes. No usa orden alfabético directamente, sino el orden en la tabla de caracteres. Va bien pero depende para qué. A mi me pone nervioso cuando en algunos sitios te encuentras algo supuestamente ordenado alfabeticamente, pero te encuentras a Óscar antes que Angel.

    El equivalente a equals() seria

    if(micadena.compareTo(micadena2)==0)

    No voy a contar mas sobre este metodo, es util para muchas cosas, os invito a leer la referencia.

  4. Comparar con Collator Más estricto y más complicado. Nos permite crear un orden alfabetico humano. Podemos configurar distintos comportamientos mediante estas constantes:
    • Collator.PRIMARY: Dos letras son la misma si la base es la misma. A=a=á=Á
    • Collator.SECUNDARY Iguales mayúsculas y minúsculas, pero distintas si tienen acento.
    • Collator.TERCIARY mayusculas, minisculas, y con acento, todas distintas
    • Collator.IDENTICALLY esto es una paranoia. Solo son iguales si tienen el mismo código interno. Es decir, si hay distintas formas de codificar á, dos á que se consideren distintas podrian verse exactamente igual.

    Tampoco cuento más, hay referencias muy completas por ahi.

Y esto ha sido una buena forma de cogerme 5 minutos de descanso.

27/05/2011
by carlosrobles
0 comments

Capturar boton volver para mostrar menu de confirmación

Esto no es nada del otro mundo, por lo general no hace falta porque cuando un usuario pulsa el boton atras, lo que quieres es precisamente volver atras, y rápido. Pero a veces hace falta por ejemplo cuando estamos una Activity seria y no queremos que por error el usuario vuelva atrás por pulsar el boton sin querer o lo que sea.

La idea es sobreescribir el método onKeyDown() de la Activity, y detectar la pulsacion del boton concretamente KeyEvent.KEYCODE_BACK

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {

  if (keyCode == KeyEvent.KEYCODE_BACK) {

    new AlertDialog.Builder(this)
      .setIcon(android.R.drawable.ic_dialog_alert)
      .setTitle("Salir")
      .setMessage("Estás seguro?")
      .setNegativeButton(android.R.string.cancel, null)//sin listener
      .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {//un listener que al pulsar, cierre la aplicacion
        @Override
        public void onClick(DialogInterface dialog, int which){
          //Salir
          TheActivity.this.finish();
        }
      })
      .show();

    // Si el listener devuelve true, significa que el evento esta procesado, y nadie debe hacer nada mas
    return true;
  }
//para las demas cosas, se reenvia el evento al listener habitual
  return super.onKeyDown(keyCode, event);
}

si programamos para android 2.0, podemos sobreescribir el listener onBackPressed() que ya se llama directamente al pulsar ese boton.

12/05/2011
by carlosrobles
1 Comment

params.ini cambia a unwritable despues de guardar cambios

A veces cuando estas editando los parametros de una plantilla en Joomla, te encuentras con que despues de guardar cambios, estos se guardan correctamente, pero a continuacion el archivo se vuelve unwriteable. Parece que por arte de magia alguien ha cambiado los permisos. De hecho no es un mensaje de error equivocado, si vas a tu ftp podras comprobar que efectivamente los permisos del archivo cambian a 555 (rxrxrx) y no importa que lo vuelvas a pasar a 755 o 777, despues de cada cambio, otra vez desaparecen los permisos de escritura.
Si buscas en internet te puedes desesperar. En casi todas partes te dicen que es un error falso, o peor aun…te dicen como cambiar los permisos por ftp, que te hace sentir esa impotencia de que te traten de novato y no te solucionen nada.

Yo no soy experto en Joomla, pero he llegado a una conclusión, leyendo un poco de código (gracias al “Find in: Entire current local site” de dreamweaver, y sabiendo un poco que palabras buscar, y con media hora de ser paciente.)
Como ya se sabe, en Joomla 1.5 hay una nueva medida de seguridad, que hace que params.ini se haga de solo lectura siempre que no se este usando, y solo se habiliten los permisos de escritura cuando se va ha cambiar algun parametro desde el admin. Esto no se si es muy util o no, pero en cualquier caso, problemas trae: siempre cambia los permisos a 555 correctamente, pero a veces no es capaz de volver a cambiarlos para que podamos escribir.
A mi me da la impresion de que es un asunto de usuarios, tal vez haya que tener cuidado con subir el template con el usuario ftp correcto, o vete a saber, pero la verdad es que preferí no darle de masiadas vueltas a eso.

En cambio, busque el archivo que se encarga de esa nueva  funcionalidad, y aunque eso seguro que me pone en contra de los puritanos…la deshabilité. Es rapido , funciona, y no creo que en la práctica tenga ninguna desventaja.

En fin, que lo recomiendo. Yo ahora estoy en 1.5.22, en otras versiones la estructura del admin es distinta, pero no mucho, lo encontrareis. En esta version, se trata de buscar el archivo controller.php, dentro de /administrator/components/com_templates/

Alrededor de la linea 226 encontramos este código:

			// Try to make the params file unwriteable
			if (!$ftp['enabled'] && JPath::isOwner($file) && !JPath::setPermissions($file, '0555')) {
				JError::raiseNotice('SOME_ERROR_CODE', JText::_('Could not make the template parameter file unwritable'));
			}

pues una de dos, o cambiamos el 0555 por 0777 (que es trabajar a lo tonto), o directamente lo comentamos entero. Y el dichoso problema desaparece de raíz.