Guardar artículo como borrador en eggBlog

Sigo con la ampliación de funcionalidades de eggBlog. En este caso he añadido la posibilidad de guardar borradores de artículos. Hacía tiempo que quería hacer esta modificación, sobre todo porque tiendo a escribir artículos kilométricos que tardo varios días en terminar, y en ocasiones tengo alguna idea buena para un artículo que quiero empezar a escribir y finalizar más adelante.

Pensaba que esta modificación sería sencilla, pero no lo ha sido porque hay que modificar muchas pequeñas cosas en muchos sitios distintos. Incluso ahora que estoy un par de días haciendo pruebas, sigo tenido la sensación de que me estoy dejando algo.

Las módificaciones a realizar son las siguientes:

Primero añadimos una nueva traducción a langs/es.php:

$lang['draft'] = "Borrador";

He añadido un par de funciones útiles a _lib/util.php. La primera comprueba si el usuario actual es el administración:

function userIsAdmin() {
    return !empty( $_SESSION['user_id-' . $_SERVER['SERVER_NAME']] ) &&
    eb_checkadmin( $_SESSION['user_id-' . $_SERVER['SERVER_NAME']] );
}

La segunda comprueba si un articulo debe ser visible para el usuario. El administrador debe tener acceso a todos los artículos, pero el resto de usuarios no deben poder ver los borradores:

function articleIsVisibleToUser( $articleId ) {
    settype( $articleId, "integer" );

    if ( userIsAdmin() ) {
        return true;
    }

    $sql = "SELECT article_flag FROM eb_articles"
    ." WHERE article_id = " . $articleId;

    if( $row = mysql_fetch_array( mysql_query( $sql ) ) ) {
        $articleFlag = $row['article_flag'];

        if( $articleFlag == 1 || $articleFlag == 2 ) {
            return true;
        }
    }

    return false;
}

En _lib/news.php tenemos un montón de cambios desperdigados. Esto se debe al nuevo estado que puede tener un artículo. Los estados, incluido el nuevo son:

  • Artículo eliminado (Es decir, los artículos no se eliminan, solo se ocultan)
  • Artículo normal
  • Artículo destacado (Aparecería al inicio de todas las páginas)
  • Borrador de artículo

Para los ficheros con muchos cambios de código, he diseñado una nueva forma para mostrar las diferencias. A ver que tal queda:

Archivo: _lib/news.php - Método: eb_articlelist($n)

$showDrafts = userIsAdmin();

$sql = "SELECT article_id, article_title, article_date, article_flag"
." FROM eb_articles WHERE article_flag = 1";
if( $showDrafts == true ){
    $sql .= " OR article_flag = 3";
}
$sql .= " ORDER BY article_date DESC";

$query = mysql_query( $sql );

Archivo: _lib/news.php – Método: eb_articlelist($n)

.eb_links_article( $row['article_title'], $row['article_id'] ) . '"';
if( $row['article_flag'] == 3 ) {
    $output .= ' style="color:red"';
}
$output .= '>' . $row['article_title'] . '</a></li>';

Archivo: _lib/news.php – Método: eb_articles($n)

$showDrafts = userIsAdmin();

$output=array();
$sql = "SELECT article_id FROM eb_articles WHERE article_flag=1";
if( $showDrafts == true ) {
    $sql .= " OR article_flag = 3";
}    
$sql .= " ORDER BY article_date DESC LIMIT " . $n;

Archivo: _lib/news.php – Método: getArticlesIdsForPage( $page, $n )

$showDrafts = userIsAdmin();

$articleIds = array();
$sql = "SELECT article_id FROM eb_articles WHERE article_flag = 1";
if( $showDrafts == true ) {
    $sql .= " OR article_flag = 3";
}
$sql .= " ORDER BY article_date DESC";

Archivo: _lib/news.php – Método: eb_article( $comments, $id )

$sql = "SELECT article_id, article_date, article_comments, article_title"
.", article_body, user_name, article_flag"
." FROM eb_articles AS a LEFT JOIN eb_users AS u ON a.author_id = u.user_id"
." WHERE article_id = ".$id." ORDER BY article_date DESC";

Archivo: _lib/news.php – Método: eb_article( $comments, $id )


$output .= " <div class="article"> ";

$draftLabel = "";
if( $row['article_flag'] == 3 ) {
    $draftLabel = "<span style="color: red;">(Borrador)</span>";
}

Archivo: _lib/news.php – Método: eb_article( $comments, $id )

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

Archivo: _lib/news.php – Método: eb_article( $comments, $id )

if(userIsAdmin()){
    $output.=" <a href="admin.php?editarticle=" . $row[0]."">"
    .$lang['edit']." | ."">".$lang['delete']."</a>";
}

Archivo: _lib/news.php – Método: eb_article($comments,$id)

if(userIsAdmin()) {
    $output .= " <a href="admin.php?editcomment=" . $comment[3]
    ."&article=" . $row[0] . "">" . $lang['edit']
    ."</a> | <a href="admin.php?deletecomment=" . $comment[3]
    ."&article=" . $row[0] . "">" . $lang['delete'] . "</a>";
}

Archivo: _lib/news.php – Método: eb_tags($n)

$sql="SELECT tag_name, COUNT(eb_tags_links.article_id) as cnt"
." FROM eb_tags_links LEFT JOIN eb_tags"
." ON eb_tags.tag_id = eb_tags_links.tag_id LEFT JOIN eb_articles"
." ON eb_articles.article_id = eb_tags_links.article_id"
." WHERE article_flag = 1 OR article_flag = 2"
." GROUP BY eb_tags.tag_id ORDER BY cnt";

Archivo: _lib/news.php – Método: eb_tag($n)

$sql="SELECT eb_articles.article_id, article_title"
." FROM eb_articles LEFT JOIN eb_tags_links"
." ON eb_articles.article_id = eb_tags_links.article_id"
." LEFT JOIN eb_tags ON eb_tags.tag_id = eb_tags_links.tag_id"
." WHERE tag_name="" . $tag . "" AND article_flag = 1 OR article_flag = 2"
." ORDER BY article_title";

Como podéis ver no mentía, hay muchos cambios en _lib/news.php. Espero que el nuevo formato permita ver mejor las diferencias entre el código original y el modificado.

Sigamos con los cambios. Ahora le toca a _lib/admin.php. Aquí los cambios están concentrados en los métodos de crear y editar un artículo, por lo que mejor pongo los métodos enteros:

function eb_admin_editarticle( $id ) {
    global $lang;
    $output=" <h3>" . ucwords( $lang['admin'] ) . "</h3> ";
    if( !empty( $_POST['text'] ) ) {
        foreach( $_POST as $key => $value ) {
            $_POST[$key] = str_replace( "&Acirc;", "", $value );
        }
        if( strlen( $_POST['tags'] ) > 1 ) {
            mysql_query( "DELETE FROM eb_tags_links WHERE article_id=" . $id );
            $tags = explode( ",", $_POST['tags'] );
            foreach( $tags as $tag ) {
                $sql = "INSERT INTO eb_tags SET tag_name="" . $tag . """;
                mysql_query( $sql );
                if( mysql_affected_rows() == 1 ) {
                    $tag_ids[] = mysql_insert_id();
                }
                else{
                    $tag_ids[] = mysql_result(
                        mysql_query( "SELECT tag_id FROM eb_tags"
                                  ." WHERE tag_name="" . $tag . """), 0 );
                }
            }
            foreach( $tag_ids as $tag_id ) {
                mysql_query( "INSERT INTO eb_tags_links"
                          ." SET tag_id=" . $tag_id . ", article_id=" . $id );
            }
        }

        $creationDateIfDraft = "";

        $sql = "SELECT article_flag FROM eb_articles"
        ." WHERE article_id = " . $id;

        if( $row = mysql_fetch_array( mysql_query( $sql ) ) ) {
            $articleFlag = $row['article_flag'];
            if( $articleFlag == 3 ) {
                $creationDateIfDraft = "", article_date="" . time();
            }
        }

        $articleStatus = $_POST['sticky'];
        if( !empty( $_POST['saveAsDraft'] ) && $_POST['saveAsDraft'] == "yes" ) {
            $articleStatus = 3;
            $creationDateIfDraft = "", article_date="" . time();
        }

        $sql = "UPDATE eb_articles SET article_flag=" . $articleStatus
        .", article_title="" . str_replace( " ", "", $_POST['title'] )
        ."", article_body="" . $_POST['text']
        ."", article_comments=" . $_POST['comments']
        .", mod_date="" . time()
        .$creationDateIfDraft
        ."" WHERE article_id=" . $id;

        mysql_query( $sql );
        header( 'Location: news.php?id=' . $id );
    }
    else {
        $sql="SELECT article_title, article_body, article_comments, article_flag"
        ." FROM eb_articles WHERE article_id=" . $id;
        $query = mysql_query( $sql );
        $row = mysql_fetch_row( $query );

        $tags = "SELECT tag_name FROM eb_tags LEFT JOIN eb_tags_links"
        ." ON eb_tags_links.tag_id = eb_tags.tag_id WHERE article_id=" . $id;
        $tags = mysql_query( $tags );
        if( mysql_num_rows( $tags ) > 0 ) {
            while( $tags_row = mysql_fetch_array( $tags ) ) $tag_array[] = $tags_row[0];
            $tags = implode( ",", $tag_array );
        }
        else $tags="";

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

        $output .= " <form action="admin.php?editarticle="
        .$id . "" method="post"> ";
        $output .= " <p><b>" . ucwords( $lang['title'] )
        ."</b><br /><input class="inputtext" type="text" name="title""
        ." value="" . $row[0] . "" /></p> ";
        $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>';
        $output .= " <p><b>" . ucwords( $lang['tags'] ) . "</b><br />"
        .$lang['tag_seperate']."<br /><input type="text" class="inputtext""
        ." name="tags" value="" . $tags . "" /></p> ";

        // Sticky radio button
        $output .= " <p><b>" . ucwords( $lang['sticky'] )
        ."</b><br /><input type="radio" name="sticky" value="1" ";
        if( $row[3] == 1 || $row[3] == 3 ) {
            $output .= " checked="checked"";
        }
        $output .= "/> " . ucwords( $lang['no'] )
        ."<br /><input type="radio" name="sticky" value="2" ";
        if( $row[3] == 2 ) {
            $output .= " checked="checked"";
        }
        $output .= "/> " . ucwords( $lang['yes'] ) . "<br /></p> ";

        // Comments radio button
        $output .= " <p><b>" . ucwords( $lang['comments'] )
        ."</b><br /><input type="radio" name="comments" value="0" ";
        if( $row[2] == 0 ) $output .= " checked="checked"";
        $output .= "/> " . ucwords( $lang['no'] )
        ."<br /><input type="radio" name="comments" value="1" ";
        if( $row[2] == 1 ) $output .= " checked="checked"";
        $output .= "/> " . ucwords( $lang['yes'] ) . "<br /></p> ";
        $output .= " <input type="hidden" id="saveAsDraft""
        ." name="saveAsDraft" value="no" /> ";
        $output .= " <p><input type="submit" name="submit" value=""
        .ucwords( $lang['save'] ) . "" /> ";
        $output .= " <input type="submit" name="submitDraft" value=""
        .$lang['draft'] . "" onclick="setSaveAsDraftTrue();" /></p> ";
        $output .= " </form> ";
        $output .= '<script type="text/javascript">'
        .'function setSaveAsDraftTrue(){'
        .'var saveAsDraft = document.getElementById( "saveAsDraft" );'
        .'saveAsDraft.value = "yes";'
        .'}'
        .'</script>';


        eb_head('');
        echo $output;
        eb_foot();
    }
}

function eb_admin_newarticle() {
    global $lang;
    $output = " <h3>" . ucwords( $lang['new_article'] ) . "</h3> ";
    if( !empty( $_POST['text'] ) ) {
        foreach( $_POST as $key => $value ) {
            $_POST[$key] = str_replace( "&Acirc;", "", $value);
        }
        $articleStatus = $_POST['sticky'];
        if( !empty( $_POST['saveAsDraft'] ) && $_POST['saveAsDraft'] == "yes" ) {
            $articleStatus = 3;
        }
        $sql = "INSERT INTO eb_articles SET article_flag=" . $articleStatus
        .",article_date = "" . time() . "",mod_date="" . time()
        ."",author_id = "" . $_SESSION['user_id-' . $_SERVER['SERVER_NAME']]
        ."", article_title = "" . str_replace( """, " ", $_POST['title'] )
        ."", article_body = "" . $_POST['text']
        ."", article_comments = " . $_POST['comments'];

        mysql_query( $sql );

        $article_id = mysql_insert_id();

        if( strlen( $_POST['tags'] ) > 1 ) {
            $tags = explode( ",", $_POST['tags'] );
            foreach( $tags as $tag ) {
                $sql = "INSERT INTO eb_tags SET tag_name="" . $tag . """;
                mysql_query( $sql );
                if( mysql_affected_rows() == 1 ) {
                    $tag_ids[] = mysql_insert_id();
                }
                else{
                    $tag_ids[] = mysql_result(
                        mysql_query( "SELECT tag_id FROM eb_tags"
                                  . " WHERE tag_name="" . $tag . """), 0 );
                }
            }
            foreach( $tag_ids as $tag_id ) {
                mysql_query( "INSERT INTO eb_tags_links".
                           " SET tag_id=" . $tag_id . ",article_id=" . $article_id);
            }
        }

        header( 'Location: news.php?id=' . $article_id );
    }
    else {
        $output .= '<script type="text/javascript"'
        .' src="_lib/scripts/ckeditor/ckeditor.js"></script>' . " "
        ." <form action="admin.php?id=newarticle" method="post"> "
        ." <p><b>" . ucwords( $lang['title'] ) . "</b><br />"
        ."<input class="inputtext" type="text" name="title" /></p> "
        ." <textarea class="ckeditor" id="text" name="text">"
        ."</textarea> "
        ." <p><b>".ucwords( $lang['tags'] ) . " " . $lang['tag_seperate']
        ."<br /><input type="text" class="inputtext" name="tags" /></p> "
        ." <p><b>" . ucwords( $lang['sticky'] ) . "</b><br />"
        ."<input type="radio" name="sticky" value="1" checked="checked" /> "
        .ucwords( $lang['no'] ) . "<br />"
        ."<input type="radio" name="sticky" value="2" /> "
        .ucwords( $lang['yes'] ) . "</p> "
        ." <p><b>" . ucwords( $lang['comments'] )
        ."</b><br /><input type="radio" name="comments" value="0" /> "
        .ucwords( $lang['no'] ) . "<br />"
        ."<input type="radio" name="comments" value="1""
        ." checked="checked" /> " . ucwords( $lang['yes'] ) . "</p> "
        ." <input type="hidden" id="saveAsDraft" name="saveAsDraft""
        ." value="no" /> "
        ." <p><input type="submit" name="submit" value=""
        .ucwords( $lang['save'] ) . "" /> "
        ." <input type="submit" name="submitDraft" value=""
        .$lang['draft'] . "" onclick="setSaveAsDraftTrue();" /></p> "
        ." </form> "
        .'<script type="text/javascript">'
        .'function setSaveAsDraftTrue() {'
        .'var saveAsDraft = document.getElementById( "saveAsDraft" );'
        .'saveAsDraft.value = "yes";'
        .'}'
        .'</script>';

        return $output;
    }
}

El último cambio se realiza en sitemap.php, para evitar enviar a Google las URLs de los borradores:

Archivo: sitemap.php

$sql = "SELECT article_id, mod_date, article_title, article_date"
." FROM eb_articles"
." WHERE article_flag = 1 OR article_flag = 2"
." ORDER BY article_date DESC";

Me gustaría destacar la falta de indentación en el código original. Entiendo que en PHP es importante el rendimiento, pero el código spaghetti y los métodos de 200 líneas no facilitan la tarea de modificar el código. Si EggBlog fuese un producto acabado, entendería la optimización del código, pero no es así, está falto de muchas funcionaliades y tiene aspectos muy mejorables, por lo que considero que la legibilidad del código debería ser una prioridad.

También he sacado varias conclusiones con esta modificación. La primera es que compartir el código en los artículos no es la mejor opción, por lo que me estoy planteando subir el código a algún repositorio para poder compartirlo con todos.

La otra es que no me gusta el código actual de eggBlog. Es buen código en cuanto a que funciona muy bien y tiene muy pocos fallos (aunque he encontrado algunos que comentaré en un artículo posterior), pero el problema de la legibilidad hace que cada vez que toco algo, me entren ganas de reordenarlo todo.

Supongo que esto se debe a que principalmente he programado en Java, donde me gusta mantener el código extremadamente limpio, y que soy un gran fan del refactoring, donde la legibilidad es mucho más importante que el rendimiento.

Por ello he descubierto que aún se muy poco sobre las mejores técnicas de programación en PHP, por lo que he dedicado un tiempo en buscar artículos de buenas prácticas. Más adelante haré un artículo recopilatorio con las que me han parecido más interesantes.

Bueno, lo dejo ya con el que ha sido sin duda mi artículo más largo. Por suerte he podido usar la función de borradores para escribirlo! 😀