Adaptar el editor para mostrar código HTML

[programación]

En muchos de mis artículos muestro código HTML. Esto no supone ningún problema para el editor a la hora de crear un nuevo artículo, pero cuando intento editar el contenido de un artículo anterior surgen complicaciones.

Esto ocurre por la manera en la que se está asignando el contenido HTML que se recupera de la base de datos. El contenido se está asignando al textarea de la siguiente manera:

$output.=" <textarea id="text" name="text">".$row[1]."</textarea>";

El problema de hacerlo así, es que si hay etiquetas HTML en el contenido, se están interpretando como parte del código de la página, por lo que empiezan a aparecer cajas de texto y otros elementos en medio del propio artículo.

La solución es por tanto asignar el contenido de forma dinámica con javascript, y hacer esto con CKEditor es sencillo:

$output.=" <textarea id="text" name="text"></textarea>";
$data = str_replace('"','"',$row[1]);
$data = str_replace(" ", "", $data);
$output.='<script type="text/javascript">'
.'var editor = CKEDITOR.replace( "text" );'
.'editor.setData("'.$data.'")'
.'</script>';

Con esto lo que estamos haciendo es, primero crear un textarea vacío. Después, en el contenido del artículo escapamos todas las comillas dobles, y añadimos una suma en los cambios de línea para que no salte un error de javascript. Por último definimos el CKEditor y le asignamos el contenido.

Sustituir el editor de artículos de eggBlog

[programación]

Tras casi dos meses con el blog, uno de los puntos más flojos que le veo es el editor de artículos. EggBlog trae como editor por defecto openwysiwyg. Este editor tiene unos cuantos fallos gordos que empezaban a ser insufribles. Sólo por listar algunos, metía código innecesario en algunas de las etiquetas, todo el estilismo se realiza mediante spans y como factor decisivo, al intentar editar el código HTML no muestra ni un cambio de línea, te lo muestra todo en un único bloque. Vamos, imposible hacer cambios directamente en el código en esas condiciones.

Mirando un poco el código, veo que la fecha de última modificación del editor es de 2007, pero vamos, lo veo normal, seguro que si me voy a su web hay una versión nueva para descargar….. ¡Pues no! La versión más actual es de 2007. Cuatro años abandonado… ummm, hay que cambiarlo 😛

Buscando editores WYSIWYG en javascript, veo que hay dos alternativas predominantes, TinyMCE y CKEditor. Los dos generan código XHTML, son opensource y tienen buenas críticas. ¿Por cual decidirse entonces? Mirando como integrarlos, ambos son sencillos de usar y tienen buen código por si algún día me meto a modificarlos.

Pruebo la demo de los dos, y en cuanto a funcionalidad están muy a la par. Sin embargo, el CKEditor me resulta más agradable de usar… pero ese tampoco es un factor determinante. Lo mejor será preguntar a internet 😛

Buscando en Google “TinyMCE vs CKEditor” me encuentro con este artículo que los compara.

Artículo bastante interesante, que acaba diciendo lo que yo ya había supuesto, que ambos están muy a la par, con el CKEditor un poco por encima. Sigo buscando más comparativas y el CKEditor sale victorioso de la mayoría. Mis dudas se disuelven por completo cuando veo comentarios en los que se dice que el CKEditor es más estable, y el TinyMCE suele tener pequeños bugs.

¡CKEditor es el vencedor!

Ahora hay que integrarlo. ¿Será complicado? Para nada, en 5 minutos solucionado.

Estas 3 líneas definían el viejo editor:

$output .= " <script type="text/javascript" src="_lib/openwysiwyg/scripts/wysiwyg.js"> </script>";
$output.=" <script type="text/javascript" src="_lib/openwysiwyg/scripts/wysiwyg-settings.js"></script>";
$output.=" <script type="text/javascript"> WYSIWYG.attach( 'text', small ); </script>";

Las borramos y las sustituimos por lo siguiente:

$output.='<script type="text/javascript"'
.' src="_lib/scripts/ckeditor/ckeditor.js"></script>';

Ahora solo falta indicarle al editor sobre que textarea debe trabajar. Esto es tan simple como asignar una clase al textarea, por lo que esto:

$output.=" <textarea id="text" name="text">".$row[1]."</textarea>";

Pasa a ser esto:

$output.=" <textarea class="ckeditor" id="text" name="text">"
.$row[1]."</textarea>";

Hay que hacer estos cambios en los métodos eb_admin_editarticle y eb_admin_newarticle. Y con esto ya tenemos nuevo editor. ¡A disfrutarlo!, que la verdad es que es una gozada comparado con el anterior 😀

Acordeón multinivel para el listado de artículos

[programación]

Sigo cambiando cosas que no me gustaban de EggBlog.

El listado de artículos en la columna de la derecha me parecía un poco pobre. Solo mostraba los 10 últimos, por lo que los más antiguos iban quedando relegados al olvido. Se podía acceder a una página con el listado completo de artículos, pero me parecía muy feucha.

La idea era por tanto poder acceder al listado completo de artículos desde cualquier página. Quitar la limitación de 10 artículos es sencillo, pero claro, cuando tenga 100 el listado sería enorme. Hace tiempo trabaje en el curro con un acordeón en Prototype, y me gustaba su funcionalidad, por lo que pensé que sería buena idea tener un acordeón multinivel para los artículos, de forma que se pueda desplegar por años y por meses.

Aquél que usé recuerdo que era un poco “pesado” en cuanto a rendimiento, por lo que decidí usar JQuery en esta ocasión. Buscando un poco encontré este acordeón.

Tenía buena pinta, así que lo estuve probando, y me daba problema al intentar hacerlo multinivel (no realizaba correctamente las animaciones).

Buscando el enlace he encontrado este otro.

También tiene buena pinta, pero no dice nada de multinivel. Tendré que hacer la prueba un día a ver como se comporta.

El caso es que seguí buscando y al final encontré una implementación de un acordeón multinivel que no utiliza ninguna librería por debajo. La implementación es muy simple, el js es diminuto (1,35KB) y funciona a las mil maravillas.

Para implementar el acordeón, el método que hay que modificar es eb_articlelist en el fichero news.php. En principio pensé en adaptar el código existente, pero no me gustaba nada su implementación. Básicamente se hace una consulta SQL para cada mes. Esto puede tener sentido si como máximo se van a mostrar 10 artículos, pero si se quieren mostrar todos no lo veo muy eficiente. Por eso acabé modificando el código por completo:

/**
 * Compone un acordeón con todos los artículos publicados, agrupados por meses
 * y por años.
 * @param integer $n Si su valor es cero no se muestra el acordeón.
 */
function eb_articlelist($n) {
    global $lang;
    settype( $n, "integer" );
    if( $n == 0 ){
        return "";
    }
    $output = '<div class="box"><strong>' . ucwords( $lang['articles'] ) . '</strong>';
    $output .= " " . '<ul class="acc acc_sup" id="acc_sup">';
    $lastYearShown = 0;
    $lastMonthShown = 0;
    $query = mysql_query( "SELECT article_id, article_title, article_date"
    ." FROM eb_articles WHERE article_flag != 0"
    ." ORDER BY article_date DESC" );
    $countNestedAcc = 0;
    while( $row = mysql_fetch_array( $query ) ) {
        $articleDate = $row['article_date'];
        $currentArticleYear = date( "Y", $articleDate );
        $currentArticleMonth  = date( "n", $articleDate );
        if( $currentArticleYear != $lastYearShown ) {
            if( $lastYearShown != 0 ) {
                // Cerramos el año anterior
                $output .= '</ul></div></div></li>';
            }
            // Abrimos el nuevo año
            $output .= " " . '<li><h3>' . $currentArticleYear . '</h3>'
            ." " . '<div class="acc-section"><div class="acc-content">'
            ." " . '<ul class="acc nested" id="nested_'.$countNestedAcc.'">';
            $countNestedAcc++;
            $lastYearShown = $currentArticleYear;
        }
        if( $currentArticleMonth != $lastMonthShown ) {
            if( $lastMonthShown != 0 ) {
                // Cerramos el mes anterior
                $output .= " " . '</ul></div></div></li>';
            }

            // Abrimos el nuevo mes
            $output .= " " . '<li><h3>' . ucwords( $lang['month' . $currentArticleMonth] )
            . " " . $currentArticleYear . '</h3>'
            . " " . '<div class="acc-section"><div class="acc-content"><ul>';
            $lastMonthShown = $currentArticleMonth;
        }

        $output .= " " . '<li><a href="'
        .eb_links_article( $row['article_title'], $row['article_id'] ) . '">'
        .$row[1] . '</a></li>';
    }

    // Cerramos el último mes, año y acordeon
    $output .= " " . '</ul></div></div></li>'
    ." " . '</ul></div></div></li>'
    ." " . '</ul>';

    // Inicializamos los acordeones con javascript
    $output .= " ".'<script type="text/javascript" src="_lib/scripts/script.js"></script>'
    ." " . '<script type="text/javascript">';

    // Acordeón superior
    $output.=" ".'var parentAccordion = new TINY.accordion.slider( "parentAccordion" );'
    ." " . 'parentAccordion.init( "acc_sup", "h3", 0, 0);';

    // Acordeones anidados
    for( $i = 0; $i < $countNestedAcc; $i++ ) {
        if( $i == 0 ) {
            $ex = 0;
        }
        else{
            $ex = -1;
        }
        $output .= " " . 'var nestedAcc' . $i . '=new TINY.accordion.slider( "nestedAcc' . $i . '" );'
        ." " . 'nestedAcc' . $i . ' . init( "nested_' . $i . '", "h3", 1,' . $ex . ', "acc-selected" );';
    }

    // Fin del script
    $output .= " " . '</script>';

    // Fin del div box
    $output .= '</div>';

    return $output;
}

Colocamos el script del acordeón (script.js) en la carpeta _lib/scripts, y añadimos las imágenes a themes/default/images.

Por último hay que modificar los estilos para el acordeón, por lo que añadimos los siguientes estilos a style.css:

/* ESTILOS PARA EL ACORDEÓN */
.acc_sup {
    width:205px;
    list-style:none;
    color:#033;
    margin:0 auto 40px -16px;
}

.acc_sup h3 {
    width:165px;
    border:1px solid #9ac1c9;
    padding:6px 6px 8px;
    font-weight:bold;
    margin-top:5px;
    cursor:pointer;
    background:url(images/header.gif);
    font-size: 9pt;
    margin: 0;
}

.acc_sup h3:hover { background:url( images/header_over.gif ) }
.acc_sup .acc-section {
    overflow:hidden;
    background:#fff;
    width: 180px;
}

.acc_sup .acc-content {
    width:162px;
    padding:15px;
    border:1px solid #9ac1c9;
    border-top:none;
    background:#fff;
    padding-left: 0px;
}

.nested {
    width:180px;
    list-style:none;
    color:#033;
    margin-bottom:15px;
    margin-left: -7px;
}

.nested h3 {
    width:145px;
    border:1px solid #9ac1c9;
    padding:6px 6px 8px;
    font-weight:bold;
    margin-top:5px;
    cursor:pointer;
    background:url(images/header.gif);
}

.nested h3:hover { background:url( images/header_over.gif ) }

.nested .acc-section {
    overflow:hidden;
    background:#fff;
    width: 160px;
}

.nested .acc-content {
    width:127px;
    padding:15px;
    border:1px solid #9ac1c9;
    border-top:none;
    background:#fff;
}

.nested .acc-selected { background:url( images/header_over.gif ) }

Con esto me cargo la página con el listado de artículos. Total, ya no aporta ninguna funcionalidad, y para mostrar el mismo acordeón que en el resto de las páginas, ya no tenía mucho sentido 😀

Añadiendo paginación al blog

[programación]

Pese a lo que me está gustando eggBlog, tiene algunas decisiones que me parecen incomprensibles. La principal es que solo se muestren 3 artículos en la página principal y no exista ningún tipo de paginación.

Así que me puse a realizar las modificaciones necesarias para añadir paginación y que el número de artículos a mostrar en las páginas fuese configurable.

Lo primero es añadir el parámetro de configuración en config.php. En mi caso, por defecto quiero mostrar 5 artículos por página:

$config['posts_per_page'] = "5";

Para hacerlo configurable debemos modificar admin.php. El método eb_admin_config se encarga de mostrar los parámetros de configuración. En el “if” se muestran por pantalla los parámetros almacenados, mientras que el “else” se encarga de construir el formulario para modificarlos. En el if introducimos:

$output.=" <tr><th>" . ucwords( 'Artículos por página' ) . "</th><td>"
.str_replace( "&Acirc;", "", htmlentities( $config['posts_per_page'], ENT_QUOTES ) )
."</td></tr>";

Mientras que en el else introducimos:

$output.=" <tr><th><label for="">" . ucwords( 'Artículos por página' )
."</label></th><td><input class="inputtext" id="" type="text""
."name="posts_per_page" value=""
.htmlentities( $config['posts_per_page'], ENT_QUOTES )."" /></td></tr>";

De la paginación en sí, se encarga una nueva página llamada page.php. Su contenido es el siguiente:

<?php
require_once "_lib/global.php";
if( !empty( $_GET['id'] ) ) {
    settype( $_GET['id'], "integer" );
}
eb_pre();
eb_head('');
$articles = getArticlesIdsForPage( $_GET['id'], $config['posts_per_page'] );
if( count( $articles ) > 0 ) {
    echo eb_article( 0, $articles );
}

showPagination( $_GET['id'], $config['posts_per_page'] );
eb_foot();
?>

Los nuevos métodos que se encargan de la paginación se encuentran en news.php y son los siguientes:

/**
 * Obtiene un vector con los IDs de los N artículos que se deben mostrar en la
 * página indicada.
 *
 * @param integer $page El número de la página a mostrar
 * @param integer $n El número de artículos a mostrar en la página
 */
function getArticlesIdsForPage( $page, $n ) {
    settype( $page, "integer" );
    settype( $n, "integer" );
    $articleIds = array();
    $sql = "SELECT article_id FROM eb_articles WHERE article_flag = 1"
          ."ORDER BY article_date DESC";
    $query = mysql_query( $sql );
    while( $row = mysql_fetch_array( $query ) ) {
        $articleIds[] = $row['article_id'];
    }

    $startIndex = ( $page - 1 ) * $n;
    $endIndex = $startIndex + $n - 1;

    if( $startIndex > ( count( $articleIds ) - 1 ) ) {
        return array();
    }

    if($endIndex > ( count( $articleIds ) - 1 ) ){
        $endIndex = count( $articleIds ) - 1;
    }

    $output = array();

    for( $i = $startIndex; $i <= $endIndex; $i++ ){
        $output[] = $articleIds[$i];
    }

    return $output;
}

/**
 * Se encarga de mostrar los enlaces a artículos anteriores y posteriores
 * dependiendo de la página en la que se encuentra el usuario.
 *
 * @param integer $currentPage La página actual
 * @param integer $articlesPerPage El número de artículos por página
 */
function showPagination( $currentPage, $articlesPerPage ){
    settype( $currentPage, "integer" );
    if( $currentPage > 1 ){
        showPrevPageLink( $currentPage );
    }

    if( count( getArticlesIdsForPage( $currentPage + 1, $articlesPerPage ) ) > 0 ){
        showNextPageLink( $currentPage );
    }
}

/**
 * Muestra el enlace a la página anterior.
 *
 * @param integer $currentPage La página actual
 */
function showPrevPageLink( $currentPage ){
    echo '<div class="paginacionPrev"><a href="page-' . ( $currentPage - 1 )
    .'.html"><- Artículos recientes</a></div>';
}

/**
 * Muestra el enlace a la página siguiente.
 *
 * @param integer $currentPage La página actual.
 */
function showNextPageLink( $currentPage ){
    echo '<div class="paginacionNext"><a href="page-' . ( $currentPage + 1 )
    .'.html">Artículos anteriores -></a></div>';
}

Por último modificamos el index.php para utilizar el nuevo parámetro y mostrar la paginación:

<?php
require_once '_lib/global.php';

global $config;
eb_pre();
eb_head('');

$articles = eb_articles( $config['posts_per_page'] );

if( count( $articles ) > 0 ) {
    echo eb_article( 0, $articles );
}

showPagination( 1, $config['posts_per_page'] );

eb_foot();
?>

Para que la paginación también utilice URL semánticas, debemos modificar el .htaccess, añadiendo esta como la primera regla:

RewriteRule page-([0-9]+).html$ page.php?id=$1 [L]

Clonar una máquina virtual de VirtualBox

[software]

En el curro empezamos a utilizar máquinas virtuales (MV) para hacer pruebas de programas con distintas configuraciones. Nos dio muy buen resultado y poco a poco las fuimos utilizando para más cosas.

Para elegir la MV a utilizar, me encomendaron estudiar las MV disponibles. Después de probar unas cuantas, al final nos quedamos con VMWare Workstation, por ser la más estable y potente.

Ahora en casa, para hacer algunas pruebas y demos, me ha surgido la necesidad de usar una MV, y claro, pagar la licencia de VMWare está descartado.

Un amigo me recomendó usar VirtualBox. Es gratis, open source, y es muy sencilla de usar. No tenía ningún problema hasta que cuando ya tenía una MV de base sobre la que trabajar, intenté hacer una copia de la misma. Siempre es recomendable tener una MV de base para no tener que hacer una reinstalación cada vez que quieras una máquina limpia.

Con el VMWare esta operación es sencilla. Copias la carpeta con la MV y listos. En VirtualBox no es así, esta operación daría un error, ya que detectaría que el ID de la MV está duplicado…

La solución es clonar la MV. El problema es que este proceso no está muy bien documentado en la página oficial, pero encontré esta entrada donde explican el proceso paso a paso.

Más vale seguir los pasos al pie de la letra, porque me costó un par de intentos clonar la MV por intentar agilizarlo un poco. Con suerte automatizarán este proceso en próximas versiones…

Descargar versiones antiguas de la JDK

[software]

Por razones de compatibilidad, tengo que usar la jdk 1.6.0_20 en un proyecto. He estado dando vueltas buscando y resulta que hay una página donde está TODO. Y quiero decir todas las versiones de todos sus productos.

Nuevo hosting y dominio

[blog]

En realidad el dominio lo tengo desde el primer hosting, pero aun no le había sacado partido, ya que los dos hostings anteriores me ofrecían sus propios dominios, y por vaguería los he estado utilizando. ¿Cual es el problema? Pues que cada vez que cambio de server me toca hacer nueva cuenta de Google Analytics y de Webmaster Tools… y todo porque pensaba que sería complicado configurarlo, cuando era tan simple como poner los DNS… 😛

Así que gracias a CO.CC que es donde he cogido el dominio. Y gracias también a Leadhoster, donde ahora tengo el hosting y está resultando tener muy buen rendimiento. Como siempre, el rastreo de Google tendrá la última palabra.

Para encontrar este hosting he utilizado esta página con una comparativa de distintos hostings.

La verdad es que del ranking no te puedes fiar mucho, porque los tres primeros, en su versión gratuita no ofrecen ni PHP ni MySQL, así que ya me dirás que mierda de hosting gratuito es…

Comparativa de hostings gratuitos

[blog]

Bueno, como parece que voy a pasar por un buen número de hostings hasta dar con uno bueno, he decidido ir haciendo una valoración de los que vaya probando:

<th>
  Recomendado
</th>

<th>
  Motivo
</th>
<td style="color: green;">
  <strong>S&Iacute;</strong>
</td>

<td>
  Por el momento todo bien.
</td>
<td style="color:red">
  <strong>NO</strong>
</td>

<td>
  Un d&iacute;a dej&oacute; de funcionar la p&aacute;gina principal.
</td>
<td style="color:red">
  <strong>NO</strong>
</td>

<td>
  Inyecci&oacute;n de c&oacute;digo.
</td>
<td style="color:red">
  <strong>NO</strong>
</td>

<td>
  Lentitud y caidas.
</td>
Hosting
www.freehostia.com
Leadhoster
000webhost
x10hosting

[ACTUALIZACIÓN 01/10/2011] Migración de leadhoster a freehostia. El problema con la página principal parecía ser un error del servidor Apache, y es posible que se hubiese solucionado tarde o temprano, pero siendo un poco impaciente, no me pude esperar.

El server me está inyectando código

[blog]

Estaba yo tan contento con el buen rendimiento de mi servidor (000webhost), cuando he empezado a ver errores en las herramientas para webmasters de Google. En concreto saca errores al recuperar el sitemap.xml y el robots.txt. En ambos casos me da errores de sintaxis, y veo que se está insertando un código javascript que empieza por:

<!-- www.000webhost.com Analytics Code -->

Buscando esto en Google, encuentro rápidamente el motivo y la solución.

Básicamente, el server inyecta código para llevar las estadísticas de la web y comprobar si se mantiene activa. Pero claro, este script puede interferir con otros scripts de la página, así como que hace inválidos los ficheros xml.

En teoría desde esta página se puede solucionar.

Metes tus datos y deshabilita la inyección de código, pero yo lo he hecho y las herramientas de Google siguen protestando…

Así que ya he empezado a buscar un nuevo hosting (joer, este no me ha durado ni un día….)

Migración del blog

[blog]

Y esto es lo que yo quería conseguir con un blog hecho en PHP+MySQL 😀

El servidor donde tenía alojado el blog (x10hosting), estaba resultando desesperantemente lento. Los tiempos de rastreo de Google promediaban los 1834 milisegundo! Solo el export de la base de datos me habrá costado como media hora entre reintentos y descargas fallidas…

Por lo que he decidido cambiar de server. La migración ha resultado muy sencilla. Subir los ficheros al FTP, importar la base de datos y poco más, todo funcionando a la primera.

Así que aquí estoy. Nuevo server y por el momento parece que es rápido! Ya veré que dicen los tiempos de rastreo…