Desarrollo de la portada del Home

La mayor parte del desarrollo del home se encuentra en el index.php. El archivo index (php, html, etc) suele ser usado como el punto de entrada de un sitio o aplicación web. Desde un punto de vista técnico, meter mucho código en el index no es lo precisamente lo más prolijo. Sin embargo, en este caso elegí velócidad sobre prolijidad.

Artículos destacados o portada

El objetivo es implementar una sección dinámica que pueda contener de uno a ocho artículos como máximo al mismo tiempo. Voy a llamar a estos artículos como featured posts dentro del código. A su vez, quiero tener la posibilidad de destacar un artículo aún más que los otros (a este lo voy a llamar Main Featured Post).
Para desarrollar se me ocurren en dos enfoques alternativos:

  1. Usar valores meta para guardar opciones custom en la edición de posts.
  2. Implementar esta sección como un widget area y tirarle adentro widgets de los artículos.

La verdad es que no me tomo en serio la opción dos. Me parece que tendría que implementar varias clases, y quiero imprimirle velocidad al desarrollo para salir rápido con una versión de la página. Mi idea es simplemente implementar un par de checkboxes en la página de edición de posts. Estos controles guardaran un valor (1 o 0) en la tabla de posts_meta de WordPress.

Agregar opciones en la página de posts.

Una opción para marcar los featured posts regulares.

Otra para marcar el main featured post.

El código para implementar ambas funcionalidades se debe agregar en el functions.php. Primero, hay que declarar la “caja” de cada opción usando la función add_meta_box:

<?php
function register_post_assets(){
    add_meta_box('featured-post', __('Featured Post'), 'add_featured_meta_box', 'post', 'advanced', 'high');
    add_meta_box('main-post', __('Main Featured Post (Only one at a Time)'), 'add_main_meta_box', 'post', 'advanced', 'high');
}
add_action('admin_init', 'register_post_assets', 1);

Parámetros importantes de add_meta_box

  1. ID de la opción.
  2. Nombre de la caja.
  3. Función callback (dónde se configuran lo que muestra la caja).
  4. Pantalla (en dónde se mostrará)

Admin_init es un hook que se ejecuta al principio cuando el usuario entra en el admin de WordPress.

Luego implemento las funciones que configuran lo que se muestra en cada caja:

<?php
function add_featured_meta_box($post){
    $featured = get_post_meta($post->ID, '_featured-post', true);
    echo "<label for='_featured-post'>".__('Featured Post&nbsp;&nbsp;&nbsp;', 'foobar')."</label>";
    echo "<input type='checkbox' name='_featured-post' id='featured-post' value='1' ".($featured == 1 ? 'checked': '')."  />";
}
       
function add_main_meta_box($post){
    $main_featured = get_post_meta($post->ID, '_main-post', true);
    echo "<label for='_main-post'>".__('Main Featured Post (Only one at a Time)&nbsp;&nbsp;&nbsp;', 'foobar')."</label>";
    echo "<input type='checkbox' name='_main-post' id='main-post' value='1' ".($main_featured == 1 ? 'checked': '')."  />";
}

Finalmente, hay que implementar el código para que el valor de la opción meta se guarde en la tabla. Para eso se puede usar el hook save_post que se dispara al guardar.

<?php
function save_all_meta($post_id){
    // Do validation here for post_type, nonces, autosave, etc...
    if (isset($_REQUEST['_featured-post']))
        update_post_meta(esc_attr($post_id), '_featured-post', esc_attr($_REQUEST['_featured-post']));
    else
        update_post_meta(esc_attr($post_id), '_featured-post', esc_attr(0));
    if (isset($_REQUEST['_main-post']))
    {
        $args = array(
            'meta_key' => '_main-post',
            'meta_value' => 1
        );

        $main_featured = new WP_Query($args);

        if ($main_featured->have_posts()):
            while($main_featured->have_posts()):
                $main_featured->the_post();
                update_post_meta(esc_attr(get_the_ID()), '_main-post', esc_attr(0));
            endwhile;
        endif;
               
        update_post_meta(esc_attr($post_id), '_main-post', esc_attr($_REQUEST['_main-post']));
    }
    else
    {
        update_post_meta(esc_attr($post_id), '_main-post', esc_attr(0));
    }
           
}
add_action('save_post', 'save_all_meta');

La primera parte del código, que se ocupa de los featured posts regulares, es bastante directa: si el checkbox está seleccionado se guardará el valor 1 sino 0. Sin embargo, el código del main featured post es más complejo. Si el checkbox no está seleccionado es todo igual. Sin embargo, dado que solo puede haber un artículo destacado a la vez, necesito asegurarme de que solo el post que estoy guardando tenga esa opción con valor 1. Por eso, primero busco antes en la base de datos si algún otro artículo tiene esa opción y actualizo el valor en ese artículo a 0.

Implementar opciones meta en la portada del home.
Para esta parte del desarrollo voy a utilizar el index.php. Luego de establecer que se está cargando el home, se obtiene de la base de datos los featured posts.

<?php
if ( is_front_page() ) :
               
    $args = array(
        'meta_key' => '_main-post',
        'meta_value' => 1
    );

    $main_featured = new WP_Query($args);
               
    $there_is_main = false;
    if ($main_featured->have_posts()):
        $there_is_main = true;
        $main_featured->the_post();
        $main_post = get_post();
        $main_post_id = $main_post->ID;
    endif;
               
    if ($there_is_main)
    {
        $args = array(
            'meta_key' => '_featured-post',
            'meta_value' => 1,
            'post__not_in' => array($main_post_id)
        );
    }
    else
    {
        $args = array(
            'meta_key' => '_featured-post',
            'meta_value' => 1,
        );
    }

    $featured = new WP_Query($args);
    $featured_post_count = 0;
    if ($featured->have_posts()):
        $featured_post_count = $featured->post_count;
    endif;
    if ($there_is_main || $featured_post_count > 0)
    {
        ?>
            <div class="row border-between mt-4 bottom-line justify-content-center">

En este if estoy preguntando si existe cualquier clase de artículo featured. Si existe alguno creo la sección para la portada.

<?php
                   
$featured_post_weight_count = 0;
$two_row_policy = false;
if ($there_is_main || (!$there_is_main && $featured_post_count == 1) )
{
    $featured_post_weight_count = 2;
    if (!$there_is_main)
    {
        $featured->the_post();
        $main_post = get_post();
        $featured_post_count--;
    }
                       
    $article = new MainFeaturedArticle();
    $article->setPost($main_post);
    $article->printHTML();
    $two_row_policy = true;
}

Si hay un solo featured post lo voy a tratar como si fuera el main featured post aunque no tenga la opción de main featured post habilitada.

<?php
class MainFeaturedArticle extends FeaturedArticle{
    public function __construct()
    {
    }
   
    public function printHTML()
    {
        ?>
        <div class="col-md-4 mb-4">
       
        <div class="card border-0 mr-2 text-center">
       
            <div class="pic card border-0">
            <?php
            echo '<a href="'.get_permalink($this->wp_post).'">';
            ?><img class="card-img-top img-fluid img-overlayed" src="<?php echo get_the_post_thumbnail_url($this->wp_post, 'medium_large')?>">
            <div class="overlay"></div>
            </a>
            </div>
       
            <div class="card-block px-0">
               
                <p class="card-category-text"><a href="<?php $category_name = get_the_category( $this->wp_post->ID )[0]->name; $category_id = get_cat_ID( $category_name ); echo esc_url( get_category_link( $category_id ) ); ?>"><?php echo $category_name; ?></a></p>
                <h3 class="card-title card-title-text big-card-title-text"><a href="<?php echo get_permalink($this->wp_post); ?>"><?php echo apply_filters( 'post_title', $this->wp_post->post_title ); ?></a></h3>
                <p class="card-category-author"><a href="/contact-page/"><?php echo get_the_author_meta('user_firstname', $this->wp_post->post_author)." ".get_the_author_meta('user_lastname', $this->wp_post->post_author); ?></a></p>
            </div>
        </div>
        </div>
        <?php
    }
}

Al ser una clase hija de FeaturedArticle, el código es bastante acotado. Solo tiene una función printHTML. Esta es la función que se encarga de imprimir HTML.

<?php
if ($featured_post_count > (8 - $featured_post_weight_count))
{
    $featured_post_count = (8 - $featured_post_weight_count);
}
                       
if ($featured_post_count > 4)
{
    $two_row_policy = true;
}

De forma arbitraria decidí las siguientes reglas:

  1. Si hay un main featured post, solo voy a permitir como máximo 6 featured posts más en la portada.
  2. El artículo principal va a ocupar el doble de altura y ancho que el resto.
  3. Dado que los seres humanos normalmente solemos prestar más atención a lo que vemos en el cuadrante superior-izquierdo, el principal se va a ubicar en la posición izquierda.
  4. El resto de los artículos se distribuirá en dos filas. El número de columnas, dependerá de la cantidad de featured posts que haya. Pueden haber de 1 columna a 3.
  5. En caso de haber un número impar de featured posts regulares. Uno de ellos tendrá doble altura como el main featured, pero el mismo ancho que los regulares. El espacio extra se va a aprovechar para mostrar un fragmento del artículo.
  6. Para loops sin main featured post voy a permitir como máximo 8 artículos.
  7. Si hay de 1 a 4 featured posts, se van a mostrar en una sola fila.
  8. Si hay más de 4 se va a mostrar con la misma política que si hubiera un main featured post.


<?php
$featured_post_count_even = ($featured_post_count % 2) == 0;
for ($i = 0; $i < $featured_post_count; $i++)
{
    $article = new FeaturedArticle();
    $featured->the_post();
    $current_post = get_post();

Comienzo el loop de featured posts.

  1. Me fijo si hay un número par o impar de posts. De eso dependerá la forma en que se configurarán las propiedades del artículo.
  2. Creo la clase FeaturedArticle.
  3. Obtengo el post.

Este es el código de la clase FeaturedArticle:

<?php
class FeaturedArticle {
    protected $width = 12;
    protected $bottom_line = false;
    protected $margin_top = 0;
    protected $multi_line = false;
    protected $mini_summary = false;
    protected $wp_post;
    protected $float_right = false;
    protected $float_left = false;
   
    public function __construct()
    {
    }
   
    public function setMultiline($multi_line)
    {
        $this->multi_line = $multi_line;
    }
   
    public function getMiniSummary()
    {
        return $this->mini_summary;
    }
   
    public function setMiniSummary($mini_summary)
    {
        $this->mini_summary = $mini_summary;
    }
   
    public function getBottomLine()
    {
        return $this->bottom_line;
    }
   
    public function setBottomLine($bottom_line)
    {
        $this->bottom_line = $bottom_line;
    }
   
    public function setMarginTop($margin_top)
    {
        $this->margin_top = $margin_top;
    }
   
   
    public function setPost($post)
    {
        $this->wp_post = $post;
    }
   
    public function setRight($float)
    {
        $this->float_right = $float;
    }
   
    public function setLeft($float)
    {
        $this->float_left = $float;
    }
   
    public function printHTML()
    {
        if ($this->multi_line)
        {
        ?>
            <div class="row<?php if ($this->bottom_line){ echo ' bottom-line';} if ($this->margin_top > 0) { echo ' mt-'.$this->margin_top;}?>">
            <div class="col-md-12">
        <?php
        }
       
        echo '<div class="card border-0'.($this->float_right ? " float-right" : "").($this->float_left ? " float-left" : "").'">';
        ?>
            <div class="pic card border-0">
            <?php
            echo '<a href="'.get_permalink($this->wp_post).'">';
            ?><img class="card-img-top img-fluid img-overlayed" src="<?php echo get_the_post_thumbnail_url($this->wp_post, 'medium')?>">
            <div class="overlay"></div>
            </a>
            </div>
       
            <div class="card-block px-0">
                <h3 class="card-title card-title-text"><a href="<?php echo get_permalink($this->wp_post); ?>"><?php echo apply_filters( 'post_title', $this->wp_post->post_title ); ?></a></h3>
                <?php
                if ($this->mini_summary)
                {
                ?>
                    <p class="card-category-excerpt"><a href="<?php echo get_permalink($this->wp_post); ?>"><?php echo get_the_excerpt($this->wp_post); ?></a></p>
                <?php
                }
                ?>
                <p class="card-category-author"><a href="/contact-page/"><?php echo get_the_author_meta('user_firstname', $this->wp_post->post_author)." ".get_the_author_meta('user_lastname', $this->wp_post->post_author); ?></a></p>
            </div>
        </div>
        <?php
        if ($this->multi_line)
        {
        ?>
            </div>
            </div>
        <?php
        }
    }
}

La mayoría de los métodos son getters o setters de propiedades. Sin embargo, la función printHTML presenta varias diferencias en relación con el de MainFeaturedArticle:

  1. La propiedad multi_line, permite definir una fila extra antes de imprimir el card de Bootstrap.
  2. La propiedad mini_summary, define si se va a imprimir un fragmento del artículo.

Continuando con el código del loop de featured posts:

<?php
if ($two_row_policy)
{
    $article->setMultiline(true);
    $index_even = ($i % 2) == 0;
    if ($index_even == $featured_post_count_even)
    {
        $article->setBottomLine(true);
    }
    else
    {
        if ($i > 0)
        {
            $article->setMarginTop(4);
        }
        else
        {
            $article->setMultiline(false);
            $article->setMiniSummary(true);
        }
    }
}
                       
$article->setPost($current_post);
if (!$two_row_policy || $article->getBottomLine() || $article->getMiniSummary())
{                          
    echo '<div class="col-md-2 mb-4">';
}
                       
$article->printHTML();
if (!$two_row_policy || !$article->getBottomLine() || $article->getMiniSummary())
{
    echo '</div>';
}
  1. Por las reglas cuatro u ocho, la política de distribución de featured posts es de dos filas. En esos casos, se asigna Multiline a todos los artículos. El único caso distinto es cuando haya un número impar de artículos, el primero ocupa un espacio de dos filas y tendrá un fragmento del artículo (la línea $article->setMiniSummary(true);).
  2. En caso de que el post sea el de la fila superior se crea una fila separadora horizontal inferior.
  3. En caso de que el post sea el de la fila inferior se crea un margen superior (hay que sacar ese magic number en versiones posteriores).
  4. Se asigna el post de WordPress a las propiedades.
  5. Si no hay política de dos filas cada artículo tiene su propia columna asignada.
  6. Se imprime el HTML.
  7. El loop reinicia o llega a su final.