El problema radica en algo tan básico como quién es el dueño del fichero y quíen lo está ejecutando. Las restricciones que implica el "SAFE MODE" hacen que el moodle falle por que el proceso que ejecuta los scripts (el servidor Apache) es un "usuario" diferente al dueño de los ficheros (tu usuario del servidor FTP de tu proveedor).
La solución parece obvia: hagamos que el propietario de los ficheros sea el Apache.
Claro, criatura, claro... pero las mismas restricciones del SAFE MODE te lo van a poner dificil (más bien imposible).
El Apache al crear un fichero lo crea con su UID. Hagamos pues que el apache cree los ficheros (y directorios) y así evitaremos los conflictos del SAFE MODE.
Y otra vez parece que la pescadilla se muerde la cola... ¡Pero hay por dónde escaparse! Con un directorio de más puede hacerse.
Nos conectamos por FTP a nuestro proveedor y creamos una carpeta llamada (por ejemplo) "formacion" y le cambiamos los derechos a "esto es jauja" [CHMOD 777].
Nos descargamos el Moodle, lo descomprimimos, y por FTP lo subimos al servidor, o si nuestro proveedor nos lo permite con el panel de control, lo subimos comprimido y lo descomprimimos en el servidor (que se tarda bastante menos), y lo hacemos en esa carpeta "formación".
Ahora hacemos el milagro, cambiamos el propietario del moodle (o algo así)... Para eso subimos los dos scripts y el fichero de texto al servidor, a ese mismo directorio "formacion".
Vamos a usar 3 scripts y un fichero de texto (más o menos).
El primer script "moodle_safe_mode_workaround.php" (siempre uso nombres cortos) crea un script. Cuando se ejecute ese script no tendrá conflictos de "safe mode" pues lo ha creado el propio Apache. Para crear el script damos el primer rodeo...
El fichero de texto "script_copiador.txt" contiene un script de PHP, pero lo hemos llamado ".txt" para que el servidor no lo ejecute, sino que lo muestre, así conseguimos que el primer script lo pueda "leer" independientemente de quién sea el dueño del fichero. Aunque claro, no lo lee "a bocajarro", sino que lo pide al servidor web.
Aviso: esto está escrito con los winflis con copy-paste de un lado y otro y la "cagontal". El que quiera código "bueno y bonito" queda invitado a vestirlo de limpio.
<?php //Leemos via web el fichero script_copiador.txt //Lo leemos via web, no sea que el safe mode se moleste y no nos deje hacer un fopen o algo así $uri=$_SERVER["REQUEST_URI"]; $thisdir=substr($uri,0,strrpos($uri, "/")); $uri="http://".$_SERVER["SERVER_NAME"].$thisdir."/script_copiador.txt"; $script=file_get_contents($uri); //lo guardamos como "script_copiador_ejecutable_DOS.php", así el propietario del script es el apache. $fp = fopen($_SERVER["DOCUMENT_ROOT"].$thisdir."/script_copiador_ejecutable_DOS.php", 'w'); fwrite($fp,$script); fclose($fp); //y ahora a ajecutar el primer script (es el segundo pero lo llamé UNO, ¿Algún problema?) $uri="http://".$_SERVER["SERVER_NAME"].$thisdir."/script_copiador_ejecutable_UNO.php"; ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>moodle safe mode workaround</title> </head> <body> Ejecute ahora <a href="<?php echo $uri ?>"><?php echo $uri ?></a> </body> </html>
Ahora empieza el cachondeo propiamente dicho... los ficheros del Moodle (y los directorios) que tenemos en el servidor son del UID del FTP, igual que este script, así que para leer la estructura de directorios usamos este script, y no el que hemos creado en el paso anterior. (En realidad el script anterior y este se podrían hacer uno solo, pero no lo he hecho)
El script recorre la estructura de directorios del Moodle, tomando nota de los nombres de los ficheros y directorios, para que el último script tenga esos datos y crea el directorio donde residirá finalmente el Moodle (y tambien crea el moodledata, ya que estamos). (Y como podréis suponer, es un codigo tan bueno, limpio y elegante como el anterior, escepto la parte del copy-paste que saqué del site del PHP)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Crear directorio</title>
</head>
<body><pre>
<?php
//directorio (que cuelga de htdocs, el directorio raiz de la web) donde esta ahora el Moodle
$origen=$_SERVER["DOCUMENT_ROOT"]."/formacion/moodleOrigen";
//directorio donde quedará el Moodle
//OJO "formación" debe tener derechos universales de escritura (hacerlo 777 para evitar problemas)
$destino=$_SERVER["DOCUMENT_ROOT"]."/formacion/moodle";
$destinodata=$_SERVER["DOCUMENT_ROOT"]."/formacion/moodledata";
//Extraer la lista de ficheros a crear
clearstatcache();
$ficheros=listFiles($origen);
$directorios=listDirs($origen);
$fp = fopen('lista_ficheros', 'w');
foreach ($ficheros as $key => $value) {
fwrite($fp, $value."\n");
}
fclose($fp);
$fp = fopen('lista_directorios', 'w');
foreach ($directorios as $key => $value) {
fwrite($fp, $value."\n");
}
fclose($fp);
//Creamos directorios de destino
mkdir($destino);
mkdir($destinodata);
chmod($destino,0777);
chmod($destinodata,0777);
//No sería necesario hacerlos "777" puesto que el que lo crea ya es el Apache,
//pero se lo ponemos para poder borrarlos por ftp sin problemas.
echo 'Ejecute ahora <a href="script_copiador_ejecutable_DOS.php">script_copiador_ejecutable_DOS.php</a>';
function listDirs( $from = ".")
{
if(! is_dir($from))
return false;
$files = array();
$dirs = array( $from);
while( NULL !== ($dir = array_pop( $dirs)))
{
if( $dh = opendir($dir))
{
while( false !== ($file = readdir($dh)))
{
if( $file == "." || $file == "..")
continue;
$path = $dir . "/" . $file;
if( is_dir($path)){
$dirs[] = $path;
$files[] = $path;
}
}
closedir($dh);
}
}
return $files;
}
function listFiles( $from = ".")
{
if(! is_dir($from))
return false;
$files = array();
$dirs = array( $from);
while( NULL !== ($dir = array_pop( $dirs)))
{
if( $dh = opendir($dir))
{
while( false !== ($file = readdir($dh)))
{
if( $file == "." || $file == "..")
continue;
$path = $dir . "/" . $file;
if( is_dir($path))
$dirs[] = $path;
else
$files[] = $path;
}
closedir($dh);
}
}
return $files;
}
?> </pre>
</body>
</html>
Este ultimo script lo creó el apache en el paso 1, y los ficheros que "lee" tambien los creó el apache, asi que no da problemas de safemode, pero...
Para leer los ficheros del Moodle (y poderlos escribir siendo el usuario del apache) tenemos que hacer un poco el tonto y conectarnos por FTP, es como mirarse el ombligo, pero por internet, una solemne estupidez... pero funciona.
Al conectarte por FTP puedes leer sin problemas los ficheros que debido al SAFE MODE no puedes leer directamente desde el script, que es propiedad del usuario del apache.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Copiar ficheros</title>
</head>
<body><pre>
<?php
//directorio (que cuelga de htdocs, el directorio raiz de la web) donde esta ahora el Moodle
$origen=$_SERVER["DOCUMENT_ROOT"]."/formacion/moodleOrigen";
//directorio donde quedará el Moodle
//OJO "formación" debe tener derechos universales de escritura (hacerlo 777 para evitar problemas)
$destino=$_SERVER["DOCUMENT_ROOT"]."/formacion/moodle";
$destinodata=$_SERVER["DOCUMENT_ROOT"]."/formacion/moodledata";
//Datos de acceso de FTP
$servidorFTP="ftp.servidorquesea.com";
$ftpuname="_el_nombre_de_usuario_ftp";
$ftppass="_la contraseña del ftp";
$ftporigendir="/formacion/moodleOrigen"; //Directorio _DEL_FTP_ donde está el Moodle. OJO, INISISTO, DEL FTP
//recuperar la lista de ficheros
$ficheros=explode("\n",file_get_contents("lista_ficheros"));
$directorios=explode("\n",file_get_contents("lista_directorios"));
//Ya tenemos la lista de lo que hay, con su nombre "real"...
//ahora le quitamos la parte del "path" que corresponde al servidor web y al directorio de origen.
$pos=strlen($origen);
foreach ($ficheros as $key => $value) {$ficheros[$key]=substr($value,$pos);}
foreach ($directorios as $key => $value) {$directorios[$key]=substr($value,$pos);}
//creamos el arbol de directorios
foreach ($directorios as $key => $value) {
echo "Creando directorio $destino$value<br />";
mkdir($destino.$value);
chmod($destino.$value,0777);
}
//leemos cada fichero y lo escribimos en el nuevo sitio (y ya es del Apache)
//Antes abrimos la conexión por FTP
echo "Conectando via FTP<BR>";
$conn = ftp_connect($servidorFTP);
if (!$conn) die('ERR: Unable to connect.');
if (!ftp_login($conn, $ftpuname, $ftppass)) die('ERR: Error en login.');
foreach ($ficheros as $key => $value) {
echo ("-->".$origen.$value."<br />");
if (FALSE==is_dir($origen.$value)){
if (!file_exists($destino.$value)){
if (ftp_get($conn,$destino.$value, $ftporigendir.$value, FTP_BINARY)) {
echo "Copiado $ftporigendir$value<br />";
} else {
die("Problema al copiar $ftporigendir$value");
}
chmod($destino.$value,0777);
}else{
echo "Fichero ya existe: $value<br />";
//comprobar tamaño. si !=, copiar
if(filesize($destino.$value)!=filesize($origen.$value)){
echo "Diferencia de tamaño, se vuelve a bajar $value<br />";
if (ftp_get($conn,$destino.$value, $ftporigendir.$value, FTP_BINARY)) {
echo "Copiado $ftporigendir$value<br />";
} else {
die("Problema al copiar $ftporigendir$value (de nuevo)");
}
chmod($destino.$value,0777);
}
}
}
}
//Cerramos la conexión por FTP
ftp_close($conn);
echo "Intente ejecutar ahora el instalador del Moodle. (Y recuerde hacer los deberes)";
</pre>
</body>
</html>
Funcionar, lo que se dice funcionar, parece que funciona, aunque desde luego no me responsabilizo de que eso sea así ni de que no cause algún otro efecto colateral poco (o nada) deseable. Ya sabes: lo publico "tal cual" y allá tú lo que hagas con esto.
Adrede he evitado ciertas cosas (file_put_contents, http_request...) porque son muchos los hostings con un PHP bastante cortito, y mejor un código algo más largo (y aún más feo) pero que funcione en casi cualquier sitio, ¿No te parece?. Por otro lado tambien me queda decir que estos mismos scripcillos cuchufletas deberían solucionar los problemas de SAFE MODE de Joomla y muchos otros CMSs que andan por ahí.
La función listFiles está fusilada de (archipel dot gb at online dot fr), según podreis ver en http://es.php.net/function.opendir (y listDirs es un clon despeinado). En lo que al resto se refiere podeis considerarlo "pedigüeñoware" (gimme-your-spare-coins-ware) donado al dominio público.
Pues nada, ahí queda ese parche chapucero, que pese a todo, es el que mejor funciona de todos los que he visto. (Cómo que no he visto ninguno...)
Sevilla, 10 de Julio de 2008
Miguel Pérez
Y el que quiera hacer alguna aportación al tema o mandar código un poco más limpito, aquí tiene mi email: [miguel - arroba - ytudequetequejas - punto - com]