Fabricar un complemento

Para algunos, quedarse atascado en un proyecto es la mejor manera de aprender y el objetivo es aprender a construir un complemento que se ve en las siguientes secciones, desde cero. Estemos preparados; Éste no es el ejemplo fácil 'Hola mundo' de demostración. En realidad, este es un complemento de demostración bastante completo que cubre varios conceptos en XF2.

El complemento que se quiere construir permitirá a los usuarios con el apropiado permiso "destacar" un tema y que se muestre en una nueva página. Incluso estableceremos un proceso que incluya automáticamente temas destacados en foros específicos. Se usará una nueva ruta denominada portal para esto y se configurará eventualmente como página índice así como se configurará la pestaña Inicio para que esté seleccionada cuando se visualice la página.

Crear el complemento

En todo el complemento se usará el ID de complemento Demo/Portal. Lo primero que se necesita hacer es crear el complemento, por lo que se necesita abrir la ventana de la terminal (shell) inmediata de comandos, cambiar el directorio a la raíz de la instalación de XF (en donde se encuentra cmd.php) y ejecutar el siguiente comando y escribir las respuestas que se muestran a continuación:

Terminal

$php cmd.php xf-addon:create

Introducir un ID para este complemento: Demo/Portal

Introducir un título: Demo/Portal

Introducir el ID de versión: 1000010

Cadena de versión configurada a: 1.0.0 Alpha

¿Debe estar activado este complemento? (y/n) y

Complemento creado con éxito. ¿Debe escribirse el archivo addon.json en ../src/addons/Demo/Portal/addon.json? (y/n) y

El archivo addon.json se ha escrito con éxito en ../src/addons/Demo/Portal/addon.json

¿Se necesita un archivo Setup? (y/n) y

¿Necesita soportar tu Setup ejecutar varios pasos? (y/n) y

El archivo Setup.php se ha escrito con éxito en ../src/addons/Demo/Portal/Setup.php

Escribiendo partes...

Partes escritas con éxito en ../src/addons/Demo/Portal/hashes.json

Ahora se ha creado el complemento y se habrá creado un nuevo directorio en el interior del directorio src/addons así como estará en la lista de "Complementos instalados" del CP Admin.

Uno de los archivos que se ha creado es addon.json que se ve algo así:

{
    "title": "Demo - Portal",
    "version_string": "1.0.0 Alpha",
    "version_id": 1000010
}

                    

Vamos a ampliar un poco esto:

{
    "title": "Demo - Portal",
    "description": "Complemento que se mostrará como tema destacado en la página principal del foro.", 
    "version_string": "1.0.0 Alpha",
    "version_id": 1000010,
    "dev": "lms",
    "icon": "fa-code"
}

                    

Sigue siendo bastante básico aunque ya se ha agregado una descripción, el nombre del desarrollador y se ha especificado un icono para mostrar. El icono puede ser una ruta (relativa a la raíz del complemento) o el nombre de un icono FontAwesome como se ha hecho aquí.

Crear la clase de configuración

Bien, hablando estrictamente, la clase ya ha sido creada y escrita en el archivo Setup.php aunque realmente no hay nada. Se tiene un esqueleto de clase que se ve algo así:

<?php
namespace Demo\Portal;
use XF\Db\Schema\Alter;
use XF\Db\Schema\Create;
class Setup extends \XF\AddOn\AbstractSetup
{
    use \XF\AddOn\StepRunnerInstallTrait;
    use \XF\AddOn\StepRunnerUpgradeTrait;
    use \XF\AddOn\StepRunnerUninstallTrait;
}

                    

Hablemos un poco sobre la clase Setup (de configuración). Vamos a dividir los procesos de instalación, actualización y desinstalación en pasos separados.

La característica aquí del StepRunner está encaminada al manejo cíclico del proceso a través de todos los pasos disponibles, Así que todo lo que hay que hacer es comenzar a crear esos pasos. Empezaremos agregando algún código para crear una nueva columna en la tabla xf_forum:

<?php
namespace Demo\Portal;
use XF\Db\Schema\Alter;
use XF\Db\Schema\Create;

class Setup extends \XF\AddOn\AbstractSetup
{
    use \XF\AddOn\StepRunnerInstallTrait;
    use \XF\AddOn\StepRunnerUpgradeTrait;
    use \XF\AddOn\StepRunnerUninstallTrait;
    public function installStep1()
    {
        $this->schemaManager()->alterTable('xf_forum', function(Alter $table)
        {
            $table->addColumn('demo_portal_auto_feature', 'tinyint')->setDefault(0);
        });
    }   
}

                    

se está agregando esta columna a la tabla xf_forum por lo que se puede configurar algunos foros para obtener temas automaticamente destacados cuando se crean. El nominado aquí es significativo; Las columnas agregadas a las tablas del motor de XF deben prefijarse siempre. Esto conlleva dos firmes propósitos. El primero es que hay menos riesgo de que se produzcan conflictos con nombres de columnas duplicados, en caso de que XF u otro complemento tenga razones para agregar esa columna en el futuro. La segunda es que ayuda a identificar más fácilmente qué columnas pertenecen a qué complementos en caso de que surjan problemas en el futuro.

Estando ahora aquí, se podría agregar otro paso al instalador. Para mayor brevedad, ahora se mostrará el nuevo código en lugar de toda la clase. S epondrá directamente debajo del método installStep1():

public functioninstallStep2()
{
    $this->schemaManager()->alterTable('xf_thread', function(Alter$table)
    {
        $table->addColumn('demo_portal_featured', 'tinyint')->setDefault(0);
    });
}

                    

Este paso, similar al paso de arriba, agregará una nueva columna a la tabla xf_thread. Se usará esta columna como un valor en caché para identificar rápidamente cuando un tema esta destacado o no sin tener que realizar consultas adicionales o búsqueda en la tabla xf_demo_portal_featured_thread.

Hablando de eso, hay que añadir esa tabla ahora. En este momento el paso 3 a continuación del installStep2():

public function installStep3()
{
    $this->schemaManager()->createTable('xf_demo_portal_featured_thread', function(Create$table)
    {
        $table->addColumn('thread_id', 'int');
        $table->addColumn('featured_date', 'int');
        $table->addPrimaryKey('thread_id');
    });
}

                    

En este paso se va a crear la nueva tabla. Esta tabla se usará para mantener un registro de todos los temas que han sido destacados y de cuando lo han sido.

Nota

¡No olvidarse de ejecutar uno mismo las consultas!

Los mismo principios se aplican aquí en términos de denominación. Una significativa diferencia es que todas las tablas deben ir prefijadas adicionalmente con xf_. La razón de esto es que si se realiza una instalación límpia de XF, se eliminan todas las tablas con el prefijo xf_, incluyendo las creadas por los complementos.

Extender la Entidad foro

Ya que se ha agregado una columna a la tabla xf_forum, es hora de extender la estructura de la Entidad Foro. Para hacer esto se necesita que la Entidad conozca a la nueva columna y, sobre los datos, que puede leerse y escribirse en ella a su traves.

Nota

Los siguientes pasos precisarán tener activado el Modo de desarrollo. Recuérdese configurar Demo/Portal como el valor de defaultAddOn en el config.php.

El primer paso de este proceso es crear un "Detector de eventos de código". Esto puede hacerse en el PC Admin en Desarrollo, hacer clic en el vínculo "Detectores de eventos de código" y otro clic en el botón "Agregar detector de eventos de código".

Se necesita detectar el evento entity_structure. Se usará esto para modificar la estructura predeterminada de la Entidad Foro y agregar demo_portal_auto_feature a nuestra nueva columna.

En el campo "Indicio de evento", se introducirá el nombre de la clase que se está extendiendo, ej. XF\Entity\Forum. Esto asegurará que nuestro detector sólo se ejecute en la Entidad Foro.

En la clase "Ejecutar devolución de llamada" introducir Demo\Portal\Listener y en el Método introducir forumEntityStructure.

Merece la pena agregar una descripción para explicar para qué es este detector ya que ayudará a identificarlo más fácilmente en la lista de detectores de eventos de código. "Extender la estructura XF\Entity\Forum" debe bastar. Finalmente, hay que asegurarse de que está seleccionado el complemento "Demo - Portal".

Antes de pulsar "Guardar" se necesita en la actualidad crear la clase Listener (Detector). Para ello debe crearse un archivo denominado Listener.php en src/addons/Demo/Portal. A continuación se indica el contenido de este archivo que debe ser como sigue, inicialmente. Se conocen los argumentos que esta función precisa de la documentación de debajo del selector de eventos de código.

<?php

namespace Demo\Portal;
use XF\Mvc\Entity\Entity;

class Listener
{
    public static function forumEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure)
    {
    
    }
}

                    

Advertir que la declaración use va entre el namespace y el nombre de la clase class. Se referenciará la clase aquí declarada más de una vez, por lo que declararla aquí permitirá referenciarla con un álias que es mucho más corto, en este caso, Entity.

Este código aún no hace nada en la actualidad, pero es buena hora para guardar el detector de eventos de código por lo que se hará ahora clic en el botón "Guardar".

Antes de agregar código de funcionalidad a la nueva función, es un buen momento para ver cómo funciona el sistema de salida del desarrollo. Véanse los nuevos directorios y archivos agregados al directorio de complementos. Especificamente existe un nuevo archivo JSON file en el directorio _output/code_event_listeners, que se va a mirar ahora como luce:

{
"event_id": "entity_structure",
"execute_order": 10,
"callback_class": "Demo\\Portal\\Listener",
"callback_method": "forumEntityStructure",
"active": true,
"hint": "XF\\Entity\\Forum",
"description": "Extends the XF\\Entity\\Forum structure"
}

                    

Cualquier cambio que se realice a este archivo se actualizará automáticamente.

Correcto, hay que añadir un poco más de código y volver para ello dentro de la clase Listener y agregar a continuación de la función forumEntityStructure:

$structure->columns['demo_portal_auto_feature'] = ['type'=> Entity::BOOL, 'default' => false];

                    

La Entidad Forum ahora es consciente de la nueva columna, pero existen unos pocos pasos más que se deben cuidar primero antes de poder comenzar a implementar realmente el establecimiento de valores en esa columna.

Extender la Entidad tema

Una vez más, ya que se ha agregado una nueva columna a la tabla xf_thread, debe hacerse que la entidad Thread sea consciente de eso. Esto es muy similar a lo hecho arriba.

Retomamos "Agregar detector de eventos de código" y detectar la entity_structure. El "Indicio de evento" esta vez es XF\Entity\Thread. Puede usarse como antes la misma clase de devolución de llamada (Demo\Portal\Listener) aunque en esta ocasión se denominará al método threadEntityStructure. Se agregará una descripción, similar a la anterior. Antes de guardar, debe agregarse el código, directamente a continuación de la función forumEntityStructure:

public static  functionthreadEntityStructure
{
    $structure->columns['demo_portal_featured'] = ['type' => Entity::BOOL, 'default' => false];
}

                    

Este código es más o menos idéntico al agregado a la estructura de la Entidad del Foro: la única diferencia que hay realmente es el nombre de la columna, aunque es preciso agregar algo más. Debe crearse una relación con la Entidad de modo que, más adelante, se pueda acceder a la Entidad tema destacado (que se creará en la siguiente sección). Debajo de la línea $structure->columnas agregar:

$structure->relations['FeaturedThread'] = [
    'entity' => 'Demo\Portal:FeaturedThread', 
    'type' => Entity::TO_ONE, 
    'conditions' => 'thread_id', 
    'primary' => true
];

Véase Relaciones para mayor información sobre las mismas. Pulsar "Guardar" para guardar el detector.

Crear una nueva Entidad

Arriba, se ha creado una nueva tabla en installStep3(). Es necesario crear una entidad para que interactúe y cree nuevos registros en esta tabla. Debido a que se trata de una entidad nueva, no necesitamos hacer otra cosa que crear la clase dentro del esqueleto src/addons/Demo/Portal/Entity/FeaturedThread.php, y que se verá así:

<?php
namespace Demo\Portal\Entity;
use XF\Mvc\Entity\Structure;
class FeaturedThread extends \XF\Mvc\Entity\Entity
{

}
                    

Es preciso usar esto para definir la estructura de la entidad que representa a la nueva tabla xf_demo_portal_featured_thread creada anteriormente. La estructura para esta entidad debe lucir algo así como:

public static function getStructure(Structure $structure)
{
$structure->table = 'xf_demo_portal_featured_thread';
$structure->shortName = 'XF:FeaturedThread';
$structure->primaryKey = 'thread_id';
$structure->columns = [
    'thread_id' => [
        'type' => self::UINT, 
        'required' => true
    ],
    'featured_date' => [
        'type' => self::UINT, 
        'default' => time()
    ]
];
$structure->getters = [];
$structure->relations = [
    'Thread' => [
        'entity' => 'XF:Thread', 
        'type' => self::TO_ONE, 
        'conditions' => 'thread_id', 
        'primary' => true
    ],
];
return $structure;
}
                    

El listado de columnas es, probablemente, auto explicativo y basado en MySQL escrito anteriormente para crear la tabla. Las relaciones incluyen a la relación Thread, que permite obtener which el registro de entidad relacionado con el tema (además de las relaciones con la Entidad Tema) de esta Entidad.

Modificar el formulario de edición del foro

Ahora es necesaria una vía para modificar la plantilla forum_edit para agregar una nueva casilla de verificación que, en última instancia, pueda escribir a la nueva columna que hemos creado ahora. Se hará esto creando una modificación de plantilla. Está en el PC de Admin, Aspecto y haciendo clic en Modificaciones de plantilla. Hacer clic en la pestaña "Admin" y pulsar seguido el botón "Agregar modificación de plantilla".

Agregar modificación de plantilla

En el campo "Plantilla", escribir "forum_edit". Ésta es la plantilla que se precisa modificar.

En el campo "Clave de modificación", escribir "demo_portal_forum_edit". Ésta es la clave única que identifica la modificación de plantilla. La convención establecida para esto es, cómo mínimo, mencionar el complemento seguido del nombre de la plantilla que se está modificando.

El campo "Descripción" debe contener algún texto para ayudar a identificar el propósito de esta modificación al observar el listado de modificaciones de plantilla postriormente. Algo como "Agregar casilla de verificación de auto destacado a la plantilla forum_edit template" será suficiente.

Cuando se escribe el nombre de la plantilla en el campo "Plantilla", se advertirá que los contenidos de la plantilla se muestran en una vista previa. Es preciso usar esto para identificar el lugar preferido para la casilla de verificación. La ubicación seleccionada no es sencilla para hacerla coincidir aunque se agregará la nueva casilla de verificación a continuación de la casilla de verificación require_prefix, que se ve algo así como:

<xf:checkboxrow label="" name = "require_prefix" value = "">
    <xf:option value = "1" label = "Requerir a los usuarios solicitar un prefijo">
        <xf:hint>Si se marca, los usuarios precisaran seleccionar un prefijo cuando crean un tema o actualizan su título. Esto no afecta a los moderadores ni al mover el tema.</xf:hint>
    </xf:option>
</xf:checkboxrow>

                    

Para hacerlo coincidir, se precisará hacer una "Expresión regular" de búsqueda que se seleccionará en el botón de radio. En el campo "Buscar" se escribirá lo siguiente:

/<xf:checkboxrow label="" name="require_prefix"[^>]*>.*<\/xf:checkboxrow>/sU 
                    

Y en el campo "Reemplazar":

$0

<xf:include template="demo_portal_forum_edit"/>
                    

Brevemente, la expresión regular coincidirá con la etiqueta entera <xf:checkboxrow> y le agrega la plantilla (que se creará rápidamente).Realmente no se está eliminando lo que se compara porque el $0 en el campo de reemplazo inserta el texto con el que se ha coincidido.

Puede usarse el botón "Prueba" para comprobar que el reemplazo funciona como se espera. Cuando se pulsa el botón "Prueba" aparece una superposición con la plantilla modificada. Si todo va bien, se debe destacar un área verde con la etiqueta entera <xf:include> que queremos agregar.

Nota

Una explicación detallada de cómo trabajar con expresiones regulares está más allá del alcance de esta guía aunque existen muchos recursos en línea que pueden ayudar.

Para crear la plantilla, se precisa crear un archivo denominado demo_portal_forum_edit.html en el directorio src/addons/Demo/Portal/_output/templates/admin. Debe contener lo siguiente:

<hr class = "formRowSep" />
<xf:checkboxrow label="" name="demo_portal_auto_feature" value="">
    <xf:option value="1" label="Destacar temas automáticamente en este foro">
        <xf:hint>Si se marca, cualquier tema publicado en este foro se destacará automáticamente.</xf:hint>
    </xf:option>
</xf:checkboxrow>

                    

No es necesario preocuparse por crear frases aún, ya que puede hacerse más tarde. Advertir que el atributo nombre coincide con el de la columna que se ha creado anteriormente y, más significativamente, el valor de la casilla de verificación también lee de la columna recién agregada en la Entidad foro.

Finalmente, pulsar guardar para guardar la modificación de plantilla. Si todo ha ido bien, al volver al listado de modificaciones de plantilla, se verá el sumario que mostrará 1 / 0 / 0 que indica, por lo tanto, que la modificación se aplica con éxito una sola vez. Un mejor indicador aún de que funciona como se esperaba es ir a la página "Nodos" que aparece en "Foros" en el PC de Admin y editar un foro existente. La nueva modificación de plantilla agregada debe aparecer ahora.

Extender el proceso de guardado del foro

Ya está la columna, está la interfaz para pasar una entrada a esa columna, ahora tenemos que manejar el guardado de los datos en esa columna. Para hecerlo hay que extender el controlador del foro y extender un método especial que se llama cuando se guardan los datos de un nodo y su foro. primero se creará una "extensión de clase" que se encuentra en PCA -> "Desarrollo". Hacer clic en "Agregar nueva extensión".

Aquí es necesario especificar el "nombre base de clase" que es el nombre de la clase que se está extendiendo, en este caso será XF\Admin\Controller\Forum. También es preciso especificar un "nombre de extensión de clase" que es la clase que extendera´a la clase base. Introducir esto como Demo\Portal\XF\Admin\Controller\Forum. Debe crearse esta clase antes de pulsar en guardar.

Crear un nuevo archivo en src/addons/Demo/Portal/XF/Admin/Controller denominado Forum.php. Esto puede parecer un camino bastante largo, pero se recomienda un camino como este para extender las clases. Esto permite identificar más fácilmente los archivos que representan las clases extendidas en virtud de que se encuentran en un directorio del mismo nombre que el Id del "complemento" (en este caso XF). También deja más claro qué clase ha sido extendida exáctamente ya que la estructura del directorio sigue la misma ruta que la clase predeterminada. El contenido de este archivo debe, por ahora, lucir como esto:

<?php

namespace Demo\Portal\XF\Admin\Controller;
class Forum extends XFCP_Forum
{

}
                    

Véase Extender clases y Type hinting para mayor información.

Pulsar guardar para guardar la extensión de clase. Ahora ya se puede agregar código. El método en particular que se precisa es una función protegida denominada saveTypeData. Extender un método existente en cualquier clase, es importante inspeccionar el método original por un montón de razones. La primera es asegurarse de que los argumentos usados en el método extendido, coincide con el metodo que se extiende. El segundo que es preciso conocer es el qué hace éste método. Por ejemplo, ¿debe devolver el método algún tipo en particuar o un objeto? Este es el caso en la mayorá de las acciones del controlador como se han mencionado en la sección Modificar la respuesta de acción de un controlador (correctamente). Sin embargo, a pesar de que este método está en un controlador, no es una acción de controlador en sí mismo actualmente. De hecho, este método en particular es un método "vacío"; no se espera que devuelva algo. Sin embargo, siempre hay que asegurarse de que se llama al método padre en el método que se extiende así que vamos a agregar el nuevo método en sí, sin el nuevo código que tenemos que añadir:

protected function saveTypeData(FormAction $form, \XF\Entity\Node $node, \XF\Entity\AbstractNode $data)
{
    parent::saveTypeData($form, $node, $data);
}
                    

Advertencia

Ésta particular lista de argumentos de los métodos asume que se tiene una declaración use que supone un alias de la clase completa \XF\Mvc\FormAction a sólamente FormAction. Por lo tanto, se deberá agregar esa declaración de uso. Agregar use XF\Mvc\FormAction; entre las líneas namespace y class.

Así que, ahora mismo, hemos extendido ese método y la extensión debe ser llamada, aunque ahora mismo no está haciendo otra cosa que llamar a su método padre. Ahora se necesita obtener el valor de la entrada desde la página de edición del foro y aplicarla a la Entidad $data (que en este caso es la entidad Foro).

protectedfunction saveTypeData(FormAction $form, \XF\Entity\Node $node, \XF\Entity\AbstractNode $data)
{
    parent::saveTypeData($form, $node, $data);
    $form->setup(function() use ($data)
    {
        $data->demo_portal_auto_feature = $this->filter('demo_portal_auto_feature', 'bool');
    });
}
                    

Usar el objeto FormAction permite tener varias extensiones apuntando al proceso que se ejecuta en el curso de una remisión de formulario. No está disponible para todas las acciones de controlador. Es más prevalente en el PC Admin, por ejemplo, que a menudo siguen un solo modelo del modo CRUD (Crear, leer -Read-, actualizar -Update-, eliminar -Delete-). Un montón de procesos en XF ocurren dentro de un servicio objeto que usualmente tiene puntos específicos de extensiones relativos al servicio que se está ejecutando. Éste uso particular del objeto FormAction es algo diferente de lo que normalmente se encuentra. Guardar un nodo es un proceso algo diferente porque además de trabajar con la entidad nodo, también trabajará con un tipo de nodo asociado, ej. una entidad foro. Hay acceso al objeto acción del formulario en este método, aunque se deba usar. Se ha usado aquí para agregar un comportamiento específico a la fase "Setup" del proceso. Es decir, cuando los objetos FormAction y se llama al método run(), se ejecutará a través de las diversas fases en un orden específico. No importa en qué orden se añadieran esos comportamientos al objeto, seguirán ejecutándose con el orden setup, validate, apply, complete.

El código agregado arriba permite configurar la columna demo_portal_auto_feature en la entidad foro para cualquier valor que se almacene para la entrada demo_portal_auto_feature que se ha agregado a la página de edición del foro. Ahora debe ser posible comprobar que todo esto funciona. Editar símplemente un foro a libre elección y comprobar la casilla de verificación. Pueden observarse dos cosas. Primera, al regresar a la edición del foro, la casilla de verificación debe estar seleccionada. segunda, si se mira en la tabla xf_forum correspondiente al foro editado, el campo demo_portal_auto_feature estará configurado a 1. Mantiene este valor activado para este foro, ya que eventualmente se estarás automáticamente destacando los temas de ese foro.

Configurar destacado automático de un tema

Se ha agregado una nueva columna a la entidad foro que permitirá destacar automáticamente un tema cuando se crea en este foro por lo que es hora de agregar el código que lo hará.

En XF2, se hace un fuerte uso de los objetos de servicio. Típicamente conlleva un enfoque del tipo "Configurar y funcionar"; se establece la configuración y se llama al método para que complete la acción. Usamos un objeto de servicio para configurar y realizar la creación del tema, por lo que esto es el lugar perfecto para agregar el código que se necesita. Todo comienza con otra extensión de clase, así que acude a la página "Agregar extensión de clase".

En este momento, la clase base será XF\Service\Thread\Creator y la extensión de clase será Demo\Portal\XF\Service\Thread\Creator y, acomo siempre, esta nueva clase mostrará algo como el código siguiente. Hay que crear este código en la ruta src/addons/Demo/Portal/XF/Service/Thread/Creator.php y pulsar "Guardar" para crear la extensión.

<?php

namespace Demo\Portal\XF\Service\Thread;
class Creator extends XFCP_Creator
{

}
                    

Mientras estamos aquí, también se creará otra extensión. La base será XF\Pub\Controller\Forum y la extensión de clase serça Demo\Portal\XF\Pub\Controller\Forum. Hay que crear el siguiente código en la ruta src/addons/Demo/Portal/XF/Pub/Controller/Forum.php y pulsar "Guardar":

<?php

namespace Demo\Portal\XF\Pub\Controller;
class Forum extends XFCP_Forum
{

}
                    

Últimamente se está extendiendo el método _save() en el objeto extendido creador de tema por lo que se puede destacar el tema después de haber sido creado. Para encajar con el enfoque "Configurar y funcionar", se creará un método que podrá usarse para indicar cuando debe crearse el tema como destacado o no. Se necesitan dos cosas para esto; una propiedad de clase para almacenar el valor (nulo como predeterminado) y un método público que permita establecer la propiedad.

protected $featureThread;
public function setFeatureThread($featureThread)
{
    $this->featureThread = $featureThread;
}
                    

Volviendo al nuevo controlador extendido del foro, se extenderá el método que configura el servicio creador y optar por destacar si la entidad foro tiene el conjunto de valores necesario. Recordar, antes de extender un método, se necesita conocer que se espera que se devuelva (si hay algo), y asegurarse de llamar al método padre. Si el método padre devuelve algo, esto es lo que debe devolver tras finalizar de ejecutar el código. En este caso, el método setupThreadCreate() devuelve el servicio creador, por lo que se iniciará esto como sigue:

public function setupThreadCreate(\XF\Entity\Forum $forum)
{
    /**  @var \Demo\Portal\XF\Service\Thread\Creator $creator */
    $creator = parent::setupThreadCreate($forum);
    return $creator;
}
                    

Como se esperaba, esto no hace nada actualmente; se llama el código extendido, pero lo que hace es sólo lo que devuelve de la llamada al padre. Hay que modificar el $creator para configurar el destacado si le es aplicable al foro con el que se está trabajando actualmente.

Entre las líneas $creator y return, agregar:

if($forum->demo_portal_auto_feature) {
    $creator->setFeatureThread(true);
}
                    

Ahora se puede agregar el método _save() a la clase extendida creador:

protected function _save()
{
    $thread = parent::_save();
    return $thread;
}
                    

Para asegurarse de que este tema se destaca, entre las líneas $thread y return es preciso agregar:

if($this->featureThread && $thread->discussion_state == 'visible') {
/**  @var \Demo\Portal\Entity\FeaturedThread $featuredThread */
    $featuredThread = $thread->getRelationOrDefault('FeaturedThread');
    $featuredThread->save();
    $thread->fastUpdate('demo_portal_featured', true);
}
                    

Porque la relación FeaturedThread se ha creadoanteriormente en la entidad tema, se puede usar actualmente esa relación, además, ¡para crear! Existe un método denominado getRelationOrDefault() que usaremos aquí. Se verá si esa relación devuelve actualmente algún registro existente y, si no, creará la entidad y la configurará con el valor predeterminado ¡con cada ID de tema! Esto significa que realmente necesitamos hacer poco más que obtener la relación predeterminada y guardarla para insertarla en la base de datos.

Adicionalmente, se debe configurar el campo demo_portal_featured a cierto. Ya que la entidad tema se ha guardado (cuando la clase original guarda a la entidad) se puede usar el método fastUpdate() para actualizar rápidamente el campo.

ahora se necesita probar tod esto y asegurarse de que funciona. Ir al foro al que se ha activado la opción demo_portal_auto_feature anteriormente y crear un nuevo tema. La única manera de saber si está funcionando ahora es revisar la tabla xf_demo_portal_featured_thread y al hacerlo debiera verse ¡un nuevo registro en ella!

Crear la página Portal

Todavía hay mucho trabajo por hacer antes de terminar aunque ya se pueden destacar temas, sin duda sería bueno si se pudiéran mostrar en alguna parte, así que comencemos a crear nuestra página portal.

Para hacerlo precisamos una nueva ruta pública. Vamos al PC Admin a "Desarrollo", hacemos clic en "Rutas" y en "Agregar ruta: Pública". Vamos a mantener esto muy simple, por ahora. El prefijo de ruta será "portal", el contexto de la sección será "inicio" y el controlador será "Demo\Portal:Portal".

Debemos crear ahora el controlador en la ruta src/addons/Demo/Portal/Pub/Controller/Portal.php con el siguiente contenido básico:

<?php
namespaceDemo\Portal\Pub\Controller;
class Portal extends \XF\Pub\Controller\AbstractController
{
}
                    

Queremos que se muestre nuestro portal a la gente cuando visite la página index.php?portal. Esta URL no tiene una parte "accion" - solo el prefijo de ruta que acabamos de crear. Con este objetivo, el código que precisamos agregar para mostrar la página portal, debe de estar en el método actionIndex(). El código básico necesari es:

public function actionIndex()
{
    $viewParams = [];
    return $this->view('Demo\Portal:View', 'demo_portal_view', $viewParams);
}
                    

Esto aún no funciona ya que no se ha creado la plantilla todavía, pero esto es suficiente por ahora para, por lo menos, demostrar que la ruta y el controlador se comunican entre sí. Visitar la URL index.php?portal debe mostrar un 'Error de plantilla'.

Como se ha mencionado en la sección Respuesta View, el primer argumento es una clase view, aunque no precisamos crear esta clase actualmente. Ésta clase puede haber sido extendida por otros complementos, si fuera necesario, e incluso no exisitir. El segundo argumento es la plantilla, que precisamos crear ahora en la ruta src/addons/Demo/Portal/_output/templates/public/demo_portal_view.html. Esa plantilla, por ahora, solo debe contener lo siguiente:

<xf:title>Portal</xf:title>

                    

Si visitamos la página portal ya se evita el error de plantilla y, aunque todavía habrá una página en blanco, al menos ahora se obtendrá el título "Portal".

Ahora es el momento de agregar el código que mostrará la lista de temas destacados. El primer paso es crear un repositorio con alguna consulta del finder común base. Para ello se creará un nuevo archivo en la ruta src/addons/Demo/Portal/Repository/FeaturedThread.php al que se agregará el siguiente código:

<?php
namespace Demo\Portal\Repository;
use XF\Mvc\Entity\Finder;
use XF\Mvc\Entity\Repository;
class FeaturedThread extends Repository
{
    /** * @return Finder */
    public function findFeaturedThreadsForPortalView()
    {
        $visitor = \XF::visitor();
        $finder = $this->finder('Demo\Portal:FeaturedThread');
        $finder
            ->setDefaultOrder('featured_date', 'DESC') 
            ->with('Thread', true) 
            ->with('Thread.User') 
            ->with('Thread.Forum', true) 
            ->with('Thread.Forum.Node.Permissions|'. $visitor->permission_combination_id) 
            ->with('Thread.FirstPost', true) 
            ->with('Thread.FirstPost.User') 
            ->where('Thread.discussion_type', '<>', 'redirect') 
            ->where('Thread.discussion_state', 'visible');
    return $finder;
    }
}
                    

Lo que estamos haciendo aquí es usar el Finder para consultar todos los temas destacados, en orden inverso de featured_date, y unirlo a la tabla xf_thread y desde esa tabla unirlo a la tabla code>xf_user para el creador de temas, las tablas xf_forum, xf_post y xf_user se unen otra vez con el creador de mensajes. Hemos afirmado que el tema, foro y primer mensaje deben de existir al haber especificado true para ese argumento por lo que se realizarán como INNER JOIN mientras que las consultas de los usuarios se realizarán con un LEFT JOIN. Es posible que el creador de algunos temas y mensajes pueda no existir (por ejemplo si se han publicado automáticamente por el sistema de noticias RSS, o publicados por invitados).

También tenemos una combinación especial aquí que busca los permisos del visitante actual con la consulta. Esto reducirá el número de consultas necesarias para procesar la página portal ya que vamos a hacer una serie de cosas (más tarde) para mostrar sólo los temas destacados a los usuarios que tengan permiso para verlos.

Esto no devuelve los resultados de la consulta. Esto devuelve el objeto finder en sí mismo. Esto permite un punto de extensión claro en caso de que otro complemento precise extender nuestro código y también permite hacer cambios adicionales antes de buscar esos datos (como para establecer un límite / offset para paginación, o establecer un orden diferente).

Ahora usemos eso en nuestro método actionIndex() dentro de nuestro controlador portal. Hay que cambiar la línea $viewParams = []; por lo siguiente:

/** @var \Demo\Portal\Repository\FeaturedThread $repo */
$repo = $this->repository('Demo\Portal:FeaturedThread');
$finder = $repo->findFeaturedThreadsForPortalView();
$viewParams = ['featuredThreads' => $finder->fetch() ];

                    

En este momento, no nos preocupemos por modificar el buscador base que hemos recuperado del repositorio. En su lugar, se comenzará a ver realmente algún resultado y se actualizará la plantilla demo_portal_view del siguiente modo (detrás de las etiquetas <xf:title>):

<xf:if is = "$featuredThreads is not empty">
    <xf:foreach loop="$featuredThreads" value = "$featuredThread">
        <xf:macro name="thread_block" 
            arg-thread="" 
            arg-post="" 
            arg-featuredThread = ""
        /> 
    </xf:foreach> 

    <xf:pagenav page="{$page}" perpage="{$perPage}" total="{$total}" link="portal" wrapperclass="block"/>
<xf:else />
    <divclass="blockMessage">Aún no hay temas destacados.</div>
</xf:if>

<xf:macro name="thread_block"  arg-thread="!" arg-post="!" arg-featuredThread="!">
    <xf:css src="message.less" />

        <div class="block">
            <div class="block-container" data-xf-init="lightbox">
            <h4 class="block-header"><a href="/temas/">  </a></h4>
            <div class="block-body">
                <xf:macro name="message" arg-post="" arg-thread="" arg-featuredThread=""
                />
            </div>
            <div class="block-footer">
                <a href="/temas/">Continuar leyendo...</a>
            </div>
        </div>
    </div>
</xf:macro>

<xf:macro name="message" arg-post="!" arg-thread="!" arg-featuredThread="!">
    <div class="message message--post message--simple">
        <div class="message-inner">
            <div class="message-cell message-cell--main">
                <div class="message-content js-messageContent">
                    <div class="message-attribution">
                        <div class="contentRow contentRow--alignMiddle">
                            <div class="contentRow-figure">
                                <xf:avatar user="" size="xxs" defaultname="" href=""/>
                            </div>
                            <div class="contentRow-main contentRow-main--close">
                                <ul class="listInline listInline--bullet u-muted">
                                    <li><xf:username user=""/></li>
                                    <li>
                                        <xf:if is="$xf.options.demoPortalDefaultSort == 'featured_date'">
                                            <xf:datetime=""/>
                                        <xf:else />
                                            <xf:date time=""/>
                                        </xf:if>
                                    </li>
                                    <li><a href="/foros/"> </a></li>
                                    <li>Respuestas: 0</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                    <div class="message-userContent lbContainer js-lbContainer" data-lb-id="post-" data-lb-caption-desc=" · 1 Enero 1970 a la(s) 01:00">
                        <blockquote class="message-body">
                            
</blockquote> </div> </div> </div> </div> </div> </xf:macro>

Ahora, sin duda, aquí está pasando mucho. A pesar de que puede parecer desalentador, en su mayoría solo es el patrón para mostrar los temas destacados en un estilo razonable. Aunque hay algunas cosas a las que vale la pena prestar atención.

Comenzamos la plantilla con una condición que lee <xf:if is="$featuredThreads is not empty"> . Esto es para comprobar que el objecto devuelto por el finder contiene actualmente registros de temas destacados. Si no, se muestra un mensaje apropiado.

Si tenemos registros, necesitamos un bucle para mostrar cada uno. Para cada registro, llamamos a una macro. Las macros son porciones reutilizables de código de plantilla que se auto documentan (ver qué argumentos son compatibles) y mantienen su propio ámbito que no puede ser contaminado con argumentos de la plantilla los que llama la macro; Esto significa que las macros sólo son conscientes de los argumentos que se pasan explícitamente y del parámetro global $xf.

La macro del bloque de temas muestra el bloque básico de temas destacados y pueden llamar a otra macro para mostrar cada mensaje.

Implementar la pestaña de navegación

Es posible que haya detectado el contexto de la sección que hemos especificado como "Inicio" al configurar la ruta y que la pestaña inicio está seleccionada al visitar la página portal, o alternativamente se puede no haber visto una pestaña inicio en absoluto si no está configurado en las opciones homePageUrl. Queremos usar la pestaña inicio predeterminada en vez de crear uno nosotros mismos y tener potencialmente una pestaña duplicada.

Para hacerlo, debe usarse un detector de eventos de código que cambie la URL a portal. En el PC Admin, Desarrollo hay que hacer clic en "Detectores de evento de código" y volver a hacer clic en "Agregar detector de evento de código". Seleccionar evento home_page_url, la clase de devolución de llamada será Demo\Portal\Listener otra vez y en este caso el método se denominará homePageUrl.

El código de este nuevo método será bastante simple:

public static function homePageUrl(&$homePageUrl, \XF\Mvc\Router $router)
{
    $homePageUrl = $router->buildLink('canonical:portal');
}

Finalmente, debe considerarse cambiar la página índice a la página portal nueva. Ir a PC Admin CP, Opciones / configuración hacer clic en opciones y seguido "Información básica del sitio". Hay que cambiar la opción "Ruta de la página índice" a portal/.

Aprovechando que se está en el PC Admin, vamos a ver qué pasa ahora cuando se hace clic en el título del foro en la cabecera. Esto llevará a la página índice. Estando todo bien, ¡ésta debe ser ahora el portal! Por añadidura a eso, la pestaña Inicio debe verse y estar seleccionada.

As an optional step, you may choose to add some additional navigation entries under the home tab. But, for now, let's move on.

Destacar/vulgarizar un tema manualmente

Ahora podemos destacar nuevos temas automáticamente. ¿Podemos manualmente destacar algún tema existente? ¿O destacar temas manualmente cuando se crean cuando no está soportada el auto destacamento? Este será un buen modo de que la página actual portal se vea un poco más llena.

Para lograrlo, agregaremos una modificación de plantilla a una macro específica que es la que se usa actualmente en las respuestas, ediciones y creación del tema. Esto implicará extender el servicio editor y hacer cambios en el código existente que maneje el auto destacado.

El primer paso es una modificación de plantilla. Ir a "Agregar modificación de plantilla" (asegurarse de que esté seleccionada la pestaña "Pública" en la lista de "Modificaciones de plantilla"). La plantilla que se va a modificar en este momento es helper_thread_options, se usará como clave demo_portal_helper_thread_options y habrá que escribir una descripción razonable. Puede hacerse aquí y ahora un "Reemplazo simple" por lo que hay que dejar seleccionado su casilla de radio y en el campo "Buscar" agregar:

<xf:if is = "$thread.canLockUnlock()">
                    

En el campo "Reemplazar con" agregar:


<xf:if is="($thread.isInsert() AND !$thread.Forum.demo_portal_auto_feature AND $thread.canFeatureUnfeature()) OR ($thread.isUpdate() && $thread.canFeatureUnfeature())">
    <xf:option label=
        "demo_portal_featured" name="featured" value="1" selected=""><xf:hint>demo_portal_featured_hint</xf:hint>
        <xf:afterhtml>
            <xf:hiddenvalname="_xfSet[featured]"value="1" />
        </xf:afterhtml>
    </xf:option>
</xf:if>
$0

Esa condición es un poco larga pero nos permite mostrar la casilla de verificación bajo dos condiciones específicas: a) Si el tema no ha sido creado aún, si está desactivada en el foro la opción auto destacado y si hay permiso para destacar o b) si es una tema que ya existe y si se tiene permiso para destacar/vulgarizar.

Una "Prueba" rápida debería mostrar este código adicional que se insertará justo encima de la casilla de verificación "Abrir" de <xf:checkboxrow>. Si todo está bien, hacer clic en "Guardar".

Hay que usar directamente código de plantilla entre la modificación de aquí, porque incluyendo una plantilla (como se ha dicho antes), no funciona por esta vía en el interior de una entrada ni en el de una etiqueta fila. Se precisa crear también la frase para la etiqueta y el indicio dado que no será posible detectar esto más tarde.

Bajo "Aspecto" ir a "Frases" y hacer clic en "Agregar frase". Hay que asegurarse de que esté seleccionado el complemento. El "Título" de la primera frase será "demo_portal_featured" y su texto "Destacado". Pulsar "Guardar". Pulsar "Agregar frase" otra vez. El "Title" de la segunda frase será "demo_portal_featured_hint" y su texto será "Los temas destacados aparecerán en la página portal."

Volviendo al código de la plantilla hay que agregar en la modificación; se ha podido notar algo. Hemos llamado a un método en la entidad tema canFeatureUnfeature() y este método no existe, aún. Vamos a usar esto eventualmente para poder comprobar los permisos que deben controlar cuando un usuario puede o no manualmente destacar un tema.

Para agregar este método, necesitamos una nueva extensión de clase para la entidad XF\Entity\Thread. Por ello, hagámoslo de forma similar a como lo hemos hecho antes. La clase extendida será Demo\Portal\XF\Entity\Thread que la crearemos en la ruta src/addons/Demo/Portal/XF/Entity/Thread.php con este contenido:

<?php

namespace Demo\Portal\XF\Entity;
class Thread extends XFCP_Thread
{
    public function canFeatureUnfeature()
    {
        return true;
    }
}
                    

De acuerdo, exactamente no tenemos mucho de valor aquí, aún. Todo el método canFeatureUnfeature() consiste en devolver true ahora mismo. Más adelante, implementaremos los permisos adecuados y los agregaremos aquí.

Para probar que esto funciona hasta ahora, abriremos uno de los temas destacado previamente y seleccionaremos "Editar tema" en el menú Herramientas. Podremos ver la casilla de verificación que hemos agregado "Configurar estado del tema" marcada como "Destacado" que debe estar marcada, indicando que éste tema está realmente destacado.

Ahora podemos pasar a cambiar el servicio de editor de temas para que compruebe este valor y destaque o vulgarice acordemente. Vamos a necesitar dos nuevas extensiones de clase para esto. Volvemos a la página "Agregar extensión de clase". La primera tendrá la clase base XF\Pub\Controller\Thread y la extensión Demo\Portal\XF\Pub\Controller\Thread. La segunda tendrá la clase base XF\Service\Thread\Editor y la extensión Demo\Portal\XF\Service\Thread\Editor.

El servicio editor va a ser muy similar al servicio creador expandido creado anteriormente, por lo que lo vamos a crear en una ubicación relevante. Éste es el código para la clase extendida:


<?php
namespace Demo\Portal\XF\Service\Thread;
class Editor extends XFCP_Editor
{
    protected $featureThread;
    public function setFeatureThread($featureThread)
    {
        $this->featureThread = $featureThread;
    }
    protected function _save()
    {
        $thread = parent::_save();
        if ($this->featureThread !== null && $thread->discussion_state == 'visible')
        {
            /**  @var \Demo\Portal\Entity\FeaturedThread $featuredThread */
            $featuredThread = $thread->getRelationOrDefault('FeaturedThread');
            if ($this->featureThread)
            {
                $featuredThread->save();
                $thread->fastUpdate('demo_portal_featured', true);
            }
            else
            {
                $featuredThread->delete();
                $thread->fastUpdate('demo_portal_featured', false);
            }
        }
        return $thread;
    }
}
                    

Esto es un poco más complicado que el código en el servicio creador. Por ejemplo, puede haber situaciones en las que se edite un tema y que el usuario no tenga permiso para editarlo por lo que no se mostrarán las casillas de verificación. En estos casos, deseamos asumir que el tema debe ser vulgarizado automáticamente. Como la propiedad predeterminada de la clase $featureThread es null podemos usar esto para que esencialmente la propiedad tenga tres estados. En este caso null que permanece "sin cambios", true que destacará el tema y false que lo vulgarizará.

En caso de vulgarizar, solo eliminamos la entidad tema destacado llamando al método delete(). En ambos casos usamos el método fastUpdate() de nuevo para actualizar el valor almacenado en caché de la entidad tema que representa el estado destacado actual.

Antes de terminar el proceso de edición, se necesita agregar código al controlador extendido del tema extendiendo específicamente el método setupThreadEdit(). El código entero del controlador extendido del tema será algo así:



namespace Demo\Portal\XF\Pub\Controller;
class Thread extends XFCP_Thread
{
    public function setupThreadEdit(\XF\Entity\Thread $thread)
    {
        /**  @var \Demo\Portal\XF\Service\Thread\Editor $editor */
        $editor = parent::setupThreadEdit($thread);
        $canFeatureUnfeature = $thread->canFeatureUnfeature();
        if ($canFeatureUnfeature)
        {
            $editor->setFeatureThread($this->filter('featured', 'bool'));
        }
        return $editor;
    }
}
                    

Esto debería ser suficiente para poder editar un tema y establecer el estado a destacado (o vulgar). Si se intenta ahora, se debieran ver aparecer o desaparecer acordemente los temas de la página portal.

Es preciso extender otro método al controlador del tema que maneje, además, la situación de mostrar o no el estado de control del tema en los formularios de respuesta del tema.

Es necesario agregar este código a continuación del método setupThreadEdit() agregado arriba:

public function finalizeThreadReply(\XF\Service\Thread\Replier $replier)
{
    parent::finalizeThreadReply($replier);
    $setOptions = $this->filter('_xfSet', 'array-bool');
    if ($setOptions)
    {
        $thread = $replier->getThread();
        if ($thread->canFeatureUnfeature() && isset ($setOptions['featured']))
        {
            $replier->setFeatureThread($this->filter('featured', 'bool'));
        }
    }
}
                    

Advertir que no se nos devuelve actualmente nada en este método porque nada se espera que devuelva.

Para el paso final de destacar/vulgarizar un tema manualmente se necesita volver al controlador del foro y cambiar ligeramente el código existente de modo que si la característica no es automática, pueda hacerse manualmente en su lugar. Esto debería ser bastante sencillo. Con la cabeza puesta en el controlador extendido del foro, se reemplaza esto:

if ($forum->demo_portal_auto_feature)
{
    $creator->setFeatureThread(span class="hljs-keyword">true);
}

                    

con esto otro:


if($forum->demo_portal_auto_feature)
{
    $creator->setFeatureThread(true);
}
else
{
    $setOptions = $this->filter('_xfSet', 'array-bool');
    if($setOptions)
    {
        $thread = $creator->getThread();
        if ($thread->canFeatureUnfeature() && isset($setOptions['featured']))
        {
            $creator->setFeatureThread($this->filter('featured', 'bool'));
        }
    }
}
                    

Principalmente, esto es lo mismo que teníamos, por ejemplo, si el foro tiene activado el destacado se puede configurar el tema como destacado, de lo contrario hay que comprobar si la casilla de verificación está o no disponible y, como en otros casos, configurar esto al estado que posea la casilla de verificación.

Podemos crear tres temas para comprobar esto y asegurar que esto funciona como se espera. El primero en un foro con auto destacado activado, para comprobar que sigue funcionando, el segundo en un foro sin auto destacado activado con la casilla de verificación "Destacado" seleccionada y el tercero con esta sin seleccionar. Suponiendo que todo funcione, vamos a seguir adelante.

Mejorar la página del portal

Aunque la página portal luce razonablemente queremos mejorarla un poco.

En primer lugar, debemos ajustar el código para que sólo muestre los X temas destacados así como agregar alguna navegación de página. En este punto y si aún no se ha hecho, ¡merece la pena mostrar más temas para poder probar la paginación!.

Para comenzar, necesitamos volver al controlador del portal y agregar el código encima del método actionIndex():

$page = $this->filterPage();
$perPage = 5;
                    

La primera línea aquí es un método auxiliar especial que obtiene el número de página actual. El segundo es la cantidad de elementos a cargar por página. Normalmente esto viene de una opción, aunque estará en el código establecido a 5 por ahora.

Lo siguiente es cambiar esta línea:

$finder = $repo->findFeaturedThreadsForPortalView();
                    

a esto:

$finder = $repo->findFeaturedThreadsForPortalView()->limitByPage($page, $perPage);
                    

Esto cambia nuestra consulta para que se limite por valores página / por página definidos anteriormente. Ésto calculará automáticamente el límite correcto ($perPage) y el offset (($page - 1) * $perPage) para la página actual. A continuación, necesitamos pasar algunos parámetros más en nuestros parámetros de view por lo hay que cambiar:

$viewParams = [ 'featuredThreads' => $finder->fetch() ];
                    

a:

$viewParams= [
    'featuredThreads' => $finder->fetch(), 
    'total' => $finder->total(), 
    'page' => $page, 
    'perPage' => $perPage
];
                    

Para visualizar la navegación de página, es necesario conocer el número total de entradas, lo que se obtiene desde el finder usando el método total(), el número de página actual y cuantos temas vamos a mostrar por página.

Si regresamos al portal, sólo se verán 5 temas destacados. Sin embargo, ahora necesitamos agregar la navegación de página. Abrimos la plantilla demo_portal_view y directamente detrás de la etiqueta de cierre </xf:foreach> hay que agregar lo siguiente:

<xf:pagenavpage="{$page}" perpage="{$perPage}" total="{$total}" link="portal" wrapperclass="block"/>
                    

Recargando en este punto la página portal, siempre y cuando se tengan más de 5 temas destacados, se verá la navegación de la página debajo de la lista de temas destacados.

Otra cosa que puede ser útil para ayudar a mejorar la apariencia de esta página es agregar una barra lateral o, más exactamente, una posición de widget que se muestra en la barra lateral.

Las posiciones de Widget se agregan en el PC Admin bajo "Desarrollo". Ir a "Posiciones de widgets" y hacer clic en "Agregar posición de widget". Hay que escribir un "ID de posición" como demo_portal_view_sidebar, un "Título" como Vista portal de demo: Sidebar y una descripción apropiada. Tras asegurar que la posición está activada y está seleccionado el ID de complemento correcto, hay que hacer clic en "Guardar".

Para agregar esta posición a la plantilla, simplemente hay que agregar la siguiente etiqueta <xf:title>:

<xf:widgetpos id="demo_portal_view_sidebar" position="sidebar"/>
                    

Naturalmente no se verá una barra lateral hasta que agreguemos algún widged. Los widgets no se asignan a los complementos, por lo que los widgets creados para esta posición necesitarán agregarse a la clase Setup si se desea mostrar por defecto algún widget configurado.

En bien de la simplicidad, duplicaremos los widgets que actualmente están asignados en la posición forum_overview_sidebar (predeterminado). Por ello agregaremos esto al nuevo método installStep4() en la clase Setup:

public function installStep4()
{
    $this->createWidget(
        'demo_portal_view_members_online',
        'members_online',
        [
            'position_id' => 'demo_portal_view_sidebar', 
            'display_order' => 10
        ]
    );
    $this->createWidget(
        'demo_portal_view_new_posts', 
        'new_posts',
        [
            'position_id' => 'demo_portal_view_sidebar',
            'display_order' => 20
        ]
    );
    $this->createWidget(
        'demo_portal_view_new_profile_posts', 
        'new_profile_posts',
        [
            'position_id' => 'demo_portal_view_sidebar',
            'display_order' => 30
        ]
    );
    $this->createWidget(
        'demo_portal_view_board_totals', 
        'board_totals',
        [
            'position_id' => 'demo_portal_view_sidebar',
            'display_order' => 40
        ]
    );
    $this->createWidget(
        'demo_portal_view_share_page', 
        'share_page',
        [
            'position_id' => 'demo_portal_view_sidebar',
            'display_order' => 50
        ]
    );
}
                    

Implementar permisos y optimizaciones

Ahora mismo, se están mostrando en el portal todos los temas destacados, independientemente de si el visitante tiene permiso para verlos o no. Ésto no es lo ideal; puede haber casos en los que se desee incluir temas de determinados foros restringidos y que sólo los vean los usuarios que pueden verlos en ese foro.

Para hacerlo necesitamos cambiar el código por el "exceso" del número de registros a mostrar, filtrar los resultados no visibles y dividir la colección resultante a la cantidad real que se quiere mostrar por página. Ésto es más fácil de lo que parece.

Para comenzar hay que ir al controlador del Portal y cambiar esta línea:

->limitByPage($page, $perPage);
                    

por:

->limitByPage($page, $perPage * 3);
                    

Y seguido de esto agregar:

$featuredThreads = $finder->fetch()
    ->filter(function(\Demo\Portal\Entity\FeaturedThread $featuredThread)
        {
            return ($featuredThread->Thread->canView());
        })
    ->slice(0, $perPage, true);
                    

Y finalmente cambiar:

'featuredThreads' => $finder->fetch(),
                    

por:

'featuredThreads' => $featuredThreads,
                    

Es posible haber detectado anteriormente en la plantilla demo_portal_view que cada mensaje entregado muestra sus adjuntos también:

'attachments': $post.attach_count ? $post.Attachments : [], 
                    

Ahora mismo, ésto se encamina a realizar una consulta adicional por cada mensaje. Por lo tanto, debemos intentar realizar una sola consulta para todos los mensajes que se muestran y agregarla a los mensajes de antemano. Probablemente suene más complicado de lo que es. Sólo hay que añadir el código a continuación de la línea ->slice(0, $perPage, true);.

$threads = $featuredThreads->pluckNamed('Thread');
$posts = $threads->pluckNamed('FirstPost', 'first_post_id');
/**  @var \XF\Repository\Attachment $attachRepo */
$attachRepo = $this->repository('XF:Attachment');
$attachRepo->addAttachmentsToContent($posts, 'post');
                    

Usaremos el método pluckNamed() para obtener una colección de temas primero, de nuevo para obtener la colección de mensajes (con la clave ID del mensaje) de los temas. Una vez tenemos los mensajes, podemos pasarlos a un método especial en el repositorio de adjuntos que realiza una sola consulta e "hidrata" la relación de adjuntos de cada mensaje.

La última cosa relacionada con los permisos para terminar es crear un nuevo permiso para controlar quién puede destacar/ vulgarizar temas manualmente. Para hacerlo, en el PC de Admin bajo "Desarrollo" hay que pulsar "Definiciones de permisos" y pulsar "Agregar permiso". El "Grupo de permisos" será "forum", el "ID de Permisos" será demoPortalFeature, el "Título" debe ser Puede destacar / vulgarizar temas, establecemos la "Interfaz de grupo" a Forum moderator permissions y después elegimos el orden de visualización apropiado y nos aseguramos de que está seleccionado el complemento y hacemos cilic en "Guardar".

Para usar este permiso actualmente, necesitamos volver a la entidad extendida de tema para modificar el método canFeatureUnfeature(). Reemplazar return true; con:

return \XF::visitor()->hasNodePermission($this->node_id, 'demoPortalFeature');
                    

En este momento, dado que los permisos no tienen ningún valor predeterminado, al ir a editar cualquier tema, resulta que la casilla de verificación "Destacado" no está. Pero si uno se confiere ese permiso la casilla se muestra. ¡Todo esto demuestra que los permisos funcionan como se esperaba!

Crear algunas opciones

Actualmente solo mostramos 5 destacados por página, aunque sería agradable disponer de una opción para variar ese número. Crear opciones es fácil. Sin ser esencial, primero crearemos un nuevo grupo de opciones al que agregaremos nuevas opciones.

En el PC de Admin bajo Opciones / Configuración, haremos clic en Opciones y clic en el botón "Agregar grupo de opciones". Le daremos el "ID de grupo" demoPortal y un título como "Demo - Opciones del Portal". Se escribirá una ̀"Descripción" adecuada y el "Orden a mostrar" y se hará clic en "Guardar".

Ahora se hará clic en "Agregar opción". Se establecerá el "ID de opción" a demoPortalFeaturedPerPage, "Título" a Temas destacados por página, formato de edición a casilla, "Tipo de datos" a Entero positivo y "Valor predeterminado" a 10. Haremos clic en "Guardar".

para implementarlo volveremos al controlador del portal y se cambiará:

$perPage = 5;
                    

por:

$perPage = $this->options()->demoPortalFeaturedPerPage;
                    

Probablemente no duela agregar otra opción. Tal vez otra útil opción sea poder cambiar el orden de clasificación predeterminado de xf_demo_portal_featured_thread.feartured_date a xf_thread.post_date. Volvamos al grupo "Demo - Opciones del Portal"y se hará clic en "Agregar opción".

Se establecerá el "ID de opción" a demoPortalDefaultSort, "Título" a Orden predeterminado de clasificación y "Formato de edición" a Botones de rádio. Para el "Formato de los parámetros" se establecerá lo que sigue:

plain featured_date=demo_portal_featured_date post_date=demo_portal_post_date

Finalmente estableceremos "Valor predeterminado" a featured_date y haremos clic en "Guardar".

Es preciso crear las frases usadas para las etiquetas de los botones de rádio, similarmente a como se crearon anteriormente para la modificación de plantilla.

Establecer el valor de la opción a "Fecha del mensaje".

Hablando estrictamente, podríamos actualizar el método repositorio para usar la nueva opción diréctamente, sin embargo, merece la pena ver como funcionan los métodos personalizados del buscador. Debemos crear un archivo en la ruta src/addons/Demo/Portal/Finder/FeaturedThread.php con el siguiente contenido:

<?php

namespace Demo\Portal\Finder;
use XF\Mvc\Entity\Finder;
class FeaturedThread extends Finder
{
    public function applyFeaturedOrder($direction = 'ASC')
    {
        $options = \XF::options();
        if ($options->demoPortalDefaultSort == 'featured_date')
        {
            $this->setDefaultOrder('featured_date', $direction);
        }
        else
        {
            $this->setDefaultOrder('Thread.post_date', $direction);
        }
        return $this;
    }
}
                    

Cómo puede verse, todo lo hecho aquí ha sido crear una clase bastante básica que extiende al objeto Finder de XF y un simple método que comprueba el valor de la opción y se aplique el orden apropiado predeterminado. Ahora se puede actualizar el método repositorio para usar esto en vez.

Dentro del repositorio de temas destacados, buscar:

->setDefaultOrder('featured_date', 'DESC')
                    

y cambiarlo por:

->applyFeaturedOrder('DESC')
                    

Finalmente, probablemente tenga sentido actualizar la vista portal para que muestre el sello de tiempo apropiado - bien la fecha de destacamento bien la fecha de realización del mensaje, dependiendo del valor de la opción.

En la plantilla demo_portal_view hay que cambiar:

<li><xf:date time="{$featuredThread.featured_date}"/></li>
                    

a:

<li>
    <xf:if is = "$xf.options.demoPortalDefaultSort == 'featured_date'">
        <xf:date time="{$featuredThread.featured_date}" />
    <xf:else />
        <xf:date time="{$thread.post_date}" />
    </xf:if>
</li>
                    

Cambios en la visibilidad de vulgarizar

Para abordar esto, se necesitará modoficar la Entidad tema otra vez pero esta vez se hará con el evento entity_post_save. Tal y como se ha mencionado en El ciclo de vida de la Entidad, el método _postSave() consiste en las acciones que puede ejecutar como resultado de la inserción o actualización de una entidad. Inicialmente se podrá vulgarizar un tema cuando este ya no sea visible.

Para ello, volvamos a "Agregar detector de eventos de códigos" y detectemos en este momento el evento entity_post_save. Ahora el indicio será XF\Entity\Thread . Para la devolución de llamada a ejecutar usaremos la misma clase que antes (Demo\Portal\Listener) aunque agregaremos un método denominado threadEntityPostSave . Queda agregar este método ahora y será entonces cuando guardemos el detector:

public static  functionthreadEntityPostSave(\XF\Mvc\Entity\Entity $entity)
{
}
                    

Hagamos clic en "Guardar" para guardar el detector.

El contenido de esta función es bastante simple y se ve algo así:

if ($entity->isUpdate())
{
    $visibilityChange = $entity->isStateChanged('discussion_state', 'visible');
    if $visibilityChange == 'leave')
    {
        $featuredThread = $entity->FeaturedThread;
        if ($featuredThread)
        {
            $featuredThread->delete();
            $entity->fastUpdate('demo_portal_featured', false);
        }
    }
}
                    

Ya hemos vulgarizado temas antes aunque ahora queremos hacer una condicional según el estado del tema. Se pueden detectar los cambios de estado de un tema usando el método isStateChanged. Ésto devolverá bien enter bien leave para la columna cuyo nombre y valor se pasan. Por ejemplo, si discussion_state cambia de visible a deleted el método devolverá leave en el ejemplo de arriba.

Una vez detectado el cambio a "quitado -leaving-" del estado visible, debemos asegurarnos de que existe una relación con los temas destacados y eliminarla, actualizando el valor en la caché.

Esto cubrirá las situaciones en las que el tema es retirado de la vista pública o remitido a la cola para aprobación. Además, necesitamos cubrir la situación en que se elimina definitivamente el tema.

Para esto se necesita otro detector para el evento entity_post_delete. Lo agregaremos usando la misma clase de devolución de llamada denominada threadEntityPostDelete . Agregaremos el siguiente código a la clase detector:

public static function threadEntityPostDelete(\XF\Mvc\Entity\Entity $entity)
{
    $featuredThread = $entity->FeaturedThread;
    if($featuredThread)
    {
        $featuredThread->delete();
    }
}
                    

Detrás de hacer clic en "Guardar" para guardar el detector, convendría relalizar una prueba. Para hacerlo, sería mejor echar un ojo a la tabla xf_demo_portal_featured_thread ya que el código no mostrará los temas no visibles, aunque siempre es importante no dejar ningín dato huérfano. Sí está bien, casi hemos terminado...

Cabos sueltos finales

Hablando de datos huérfanos, se debe ordenar la base de datos siempre que se desinstale el complemento. Puede hacerse esto en la clase Setup creada anteriormente.

Crearemos 3 nuevos métodos que se corresponden con los tres primeros pasos de instalación:

public function uninstallStep1()
{
    $this->schemaManager()->alterTable('xf_forum', 
    function(Alter $table)
    {
        $table->dropColumns('demo_portal_auto_feature');
    });
}

public function uninstallStep2()
{
    $this->schemaManager()->alterTable('xf_thread', {$table->dropColumns('demo_portal_featured');});
}

public function uninstallStep3()
{
    $this->schemaManager()->dropTable('xf_demo_portal_featured_thread');
}
                    

No hay que crear un paso de desinstalación que elimine los widgets ya que se eliminan automáticamente cuando se eliminan sus posiciones. Ésto mismo es cierto para cualquier otro dato creado y asociado con el complemento -- Se eliminarán automáticamente al desinstalar.

Lanzar el complemento

El paso final de cualquier complemento ¡es lanzarlo! Esto conlleva extraer los archivos XML de la base de datos (que se empaquetan y se usan para instalarlos), calculando las partes de cada archivo y agregándolo al archivo hashes.json empaquetando sólo los archivos relevantes en un archivo ZIP file.

Amablemente, ¡ésto puede hacese con un solo comando CLI! Ejecutar el siguiente comando:

Terminal

$ php cmd.php xf-addon:build-release Demo/Portal

Realizando exportación del complemento.

Exportando datos para Demo - Portal en ../src/addons/Demo/Portal/_data.

10/10 [============================] 100%

Escrito con éxito.

Escribiendo partes...

Partes escritas con éxito en ../src/addons/Demo/Portal/hashes.json

Construyendo lanzamiento en ZIP.

Escribiendo lanzamiento en ZIP en ../src/addons/Demo/Portal/_releases.

Lanzamiento escrito con éxito.

Por esto, ¡concluye el complemento Demo! Si se desea descargar el código fuente de este complemento, construido usando los comandos explicados arriba, hacer clic aquí: Demo-Portal-1.0.0 Alpha.zip.