Importar Recursos en Liferay 7 desde el tema

Liferay provee a los desarrolladores de una herramienta que te fascilitará la importación de recursos como Pages, Structures, Templates, Applications Display Templates (ADTs), Web Content y Documents and Media a un Portal de Liferay.

Todo esto es posible gracias al modulo de importación de recursos de Liferay, mejor conocido como resource importer tool, la cual a traves del deploy de un Tema de Liferay hará toda la mágia.

Comencemos por saber que todos los recursos que deseemos importar desde el tema deben estar bajo el folder [theme-name]/src/WEB-INF/src/resources-importer.

Todos los assets a importar deben estar bajo la siguiente estrucutura de carpetas:

- WEB-INF
    -src
        -resources-importer             <---  Directorio base.
            -document_library   
                -documents              <---  Directorio donde debes agregar el contenido de tipo document and media(imágenes, videos, documentos).
            -journal
                -articles               <---  Directorio donde debes añadir el contenido web.
                -structures             <---  Directorio que contiene las estrucuturas
                -templates              <---  Directorio para los templates.
            -templates
                -application_display
                    -asset_entry        <---  Directorio para ATDs (Application Display Templates).
            sitemap.json                <---  Este archivo provee todo el mapping de los recursos que vamos a importar.

En la estrucutura anterior podemos notar que los recursos a importar estan separados por folders.

Estructuras

Las estructuras son aquellas que nos dan el esqueleto de campos ó elementos disponibles para crear contenido, campos que pueden ser de tipo texto, fecha, documento, html entre algunos otros.

Para poder añadir estos campos con el resource importer desde el tema, debemos crear un archivos con extensión JSON, el cual debe contener una propiedad array  (availableLanguageIds) para los idiomas, de igual manera tendrá una propiedad que define el idioma por defecto (defaultLanguageId) y para añadir nuestros campos tendremos nuevamente una propiedad array (fields), la cual contendrá un objecto por cada campo. Cada objeto tendrá el label, el name, el tipo de dato, propiedades booleanas que nos en las que definimos si el campo es repetible, si es indexable, si queremos mostrar el label entre algunas otras. Para tener un poco más claro esto, dale un vistazo a la estructura siguiente:

{
    "availableLanguageIds": [
        "en_US"
    ],
    "defaultLanguageId": "en_US",
    "fields": [
        {
            "label": {
                "en_US": "Summary"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": "A short, clear description that states the main facts or ideas of the content. "
            },
            "dataType": "string",
            "indexType": "keyword",
            "localizable": true,
            "name": "summary",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "textarea"
        },
        {
            "label": {
                "en_US": "Body"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": ""
            },
            "dataType": "html",
            "fieldNamespace": "ddm",
            "indexType": "keyword",
            "localizable": true,
            "name": "body",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-text-html"
        },
        {
            "label": {
                "en_US": "Thumbnail"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": "This image is shown when the item is in a list. Suggested size: 150px wide x 100px high."
            },
            "dataType": "document-library",
            "fieldNamespace": "ddm",
            "indexType": "keyword",
            "localizable": true,
            "name": "thumbnail",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-documentlibrary"
        },
        {
            "label": {
                "en_US": "Image"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": ""
            },
            "dataType": "document-library",
            "fieldNamespace": "ddm",
            "indexType": "keyword",
            "localizable": true,
            "name": "image",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-documentlibrary"
        },
        {
            "label": {
                "en_US": "Attachment"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": ""
            },
            "dataType": "document-library",
            "fieldNamespace": "ddm",
            "indexType": "",
            "localizable": false,
            "name": "attachment",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-documentlibrary"
        }
    ]
}

Plantillas (Templates)

Bien, ahora te preguntarás como puedes obtener la información de de los campos de una estructura, bueno, para eso tenemos los templates, los cuales fungen como plantillas para renderear los campos de una estrucutura.

Para añadir templates al resource importer, debemos crear un folder el cual debe de coincidir en nombre con el nombre del folder que va a contener a los articles, como por ejemplo:

Estos pueden ser escritos con sintaxis de Velocity ó Freemarker (el recomendado por Liferay) los cuales son motores para la generación de plantillas, estos separan la lógica de la vista y son server site, es decir, se compilan de lado del cliente.

<#-- start getting entry data -->
<#assign entryID = .vars['reserved-article-id'].data >
<#assign entryTitle = .vars['reserved-article-title'].data >
<#assign entryAuthor = .vars['reserved-article-author-name'].data >
<#assign entryAuthorEmail = .vars['reserved-article-author-email-address'].data >
<#assign entryPublishDate = .vars['reserved-article-display-date'].data >
<#assign entrySummary = summary.getData() >
<#assign entryBody = body.getData() >
<#assign entryImage = image.getData() >
<#if entryImage?? && entryImage != "">
    <#assign entryImageAltText = entryImage>
</#if>
<#assign entryThumbnail = thumbnail.getData()>
<#-- end getting entry data -->
<h1>${entryTitle}</h1>
  <#--<p>hello world start</p>
  <liferay-portlet:runtime portletName="com_liferay_hello_world_web_portlet_HelloWorldPortlet" />
  <p>hello world end</p>-->

<p class="text-muted">${entryAuthor} | ${entryPublishDate}</p>
<div class="well">
    <p>${entrySummary}</p>
</div>
<#if entryImage?? && entryImage != "">
    <div class="panel panel-default content-image">
        <img src="${entryImage}" alt="${entryImageAltText}" class="img-responsive">
    </div>
    <#if entryImageAltText?? && entryImageAltText != "">
        <p class="text-muted" style="font-weight: bold; font-size: .8em; margin-bottom: 20px; margin-top: -10px;"><i class="glyphicon glyphicon-camera"></i> ${entryImageAltText}</p>
    </#if>
</#if>

${entryBody}

<style>
/* hide the back arrow and title included by ful_content.jsp [https://github.com/liferay/com-liferay-asset/blob/76dcfe6d2b86be772f179223b81d9c6f2c495786/asset-publisher-web/src/main/resources/META-INF/resources/display/full_content.jsp]
   
   @todo: develop a custom version of the asset publisher that does not include this.
  */
.portlet-content-container > .portlet-body > div > div.h2 > span {
    display: none;
}
</style>

En el código anterior tenemos un template el cual obtiene el título de una noticia, el autor, el resumen (summary), el cuerpo (body), el thumbnail y una imagen. Todos estos campos son puestos entre tags de HTML para darle cuerpo y forma a nuestras noticias.

Documentos

Para el caso del directorio documents pudes agregarlos directamente bajo este los recursos de tipo imágen, video ó algún otro documento como PDF, XLS, DOCX, etc. o bien, puedes crear subfolders para separar y tener mejor ordenados estos tus ducumentos.

Contenido Web (Articles)

Para añadir contenido debes agregar un folder contenedor dentro de articles. Al igual que con los documentos, tu puedes agregar un folders extra para ordenar de mejor manera tu contenido.

Por ejemplo un folder llamado news, entonces nos quedaría algo así:

../journal/articles/web content/news

Por otro lado el contenido que deseemos añadir debe estar en formato XML.

<?xml version="1.0"?>

<root available-locales="en_US" default-locale="en_US">
	<dynamic-element name="summary" type="text_box" index-type="keyword">
		<dynamic-content language-id="en_US"><![CDATA[This is the Title]]></dynamic-content>
	</dynamic-element>
	<dynamic-element name="body" type="text_area" index-type="keyword">
		<dynamic-content language-id="en_US">
        <![CDATA[<p>This is the Content.</p>]]></dynamic-content>
	</dynamic-element>
	<dynamic-element name="thumbnail" type="document_library" index-type="keyword">
		<dynamic-content language-id="en_US" type="journal"><![CDATA[ [$FILE=content_02.jpg$] ]]></dynamic-content>
	</dynamic-element>
	<dynamic-element name="image" type="document_library" index-type="keyword">
		<dynamic-content language-id="en_US" type="journal"><![CDATA[ [$FILE=content_04.jpg$] ]]></dynamic-content>
	</dynamic-element>
</root>

Como podemos ver en el bloque anteior, todos los elementos que añadimos en nuestra estructura son nodos de XML con que agregan el contenido a traves del tag <dynamic-element> que a su vez contiene el tag <dynamic-content>. Dentro de este ultimo tag tenenos una notación particular ![CDATA[ …]] dentro de la cual va el contenido, como el summary, body y las imágenes. 

El tag <dynamic-element> tiene como atributos el nombre del elemento, el tipo y el tipo de indexamiento, los cuales fueron proporcionados en el JSON de la estrucutura.

El tag <dynamic-content> tiene como atributos el idioma y el tipo de contenido.

Aplication Display Templates (ADTs)

Los Applications Display Templates o ADTs son utiles para renderear un set de articles, los cuales pueden ser iterados usando sintaxis de Freemarker. Para poder agregar ADTs a nuestro resource importer requerimos de un folder llamdo asset_entry el cual debe estar bajo application_display. 

En la imágen anterior se puede ver la estructura de directorios explicada anteriormente. A continuación el código del ADT News and Events Archive.

<#if entries?has_content>

    <!-- start header html -->
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">All News</h3>
        </div>
        <ul class="list-group">
    <!-- end header html -->

    <!-- start item loop -->
    <#list entries as entry>
        <#assign renderer = entry.getAssetRenderer()>
        <#assign className = renderer.getClassName() >
        <#if className == "com.liferay.journal.model.JournalArticle" >
            <#assign journalArticle = renderer.getArticle() >
            <#assign document = saxReaderUtil.read(journalArticle.getContent()) >

            <!-- start item data -->
            <#assign entryViewURL = assetPublisherHelper.getAssetViewURL(renderRequest, renderResponse,entry) />
            <#assign entryTitle = entry.getTitle(locale) />
            <#assign entryPublishDate = dateUtil.getDate(entry.getPublishDate(), "dd MMM yyyy", locale) />
            <#assign entryAuthor = entry.getUserName() />

            <#assign entrySummary = document.valueOf("//dynamic-element[@name='summary']") />
            <#assign entryBody = document.valueOf("//dynamic-element[@name='body']") />
            <#assign entryThumbnail = document.valueOf("//dynamic-element[@name='thumbnail']") />
            <!-- end item  data -->

            <!-- start item html -->
            <li class="list-group-item">
                <div class="media">
                    <div class="media-left">
                        <a href="${entryViewURL}">
                            <div class="media-object" alt="..." style="width: 150px; height: 100px; background-image: url(${entryThumbnail}); background-repeat: no-repeat; background-position: center; background-size: cover"></div>
                        </a>
                    </div>
                    <div class="media-body">
                        <h4 class="media-heading"><a href="${entryViewURL}">${entryTitle}</a></h4>
                        <div class="text-muted small">${entryAuthor} | ${entryPublishDate} </div>
                        ${entrySummary}
                    </div>
                    <div class="media-right">
                        <a href="<@getEntryEditURL />"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
                    </div>
                </div>
            </li>
            <!-- end item html -->

            <!-- start macros -->
            <#macro getEntryEditURL>
                <#if renderer.hasEditPermission(themeDisplay.getPermissionChecker())>
                    <#assign redirectURL = renderResponse.createRenderURL() />

                    ${redirectURL.setParameter("struts_action", "/asset_publisher/add_asset_redirect")}
                    ${redirectURL.setWindowState("pop_up")}

                    <#assign editPortletURL = renderer.getURLEdit(renderRequest, renderResponse, windowStateFactory.getWindowState("pop_up"), redirectURL) />

                    <#if validator.isNotNull(editPortletURL)>
                        <#assign entryEditURL = "javascript:Liferay.Util.openWindow({dialog: {width: 960}, id:'" + renderResponse.getNamespace() + "editAsset', title: '" + entryTitle + "', uri:'" + htmlUtil.escapeURL(editPortletURL.toString()) + "'});" />                        
                        ${entryEditURL}
                    </#if>
                </#if>
            </#macro>
            <!-- end macros -->

        </#if>
    </#list>
    <!-- end item loop -->

    <!-- start footer -->        
        </ul>
    </div>
    <!-- end footer -->
</#if>

 

Sitemap

Pareciera que ya tenemos todo listo para importar nuestros recurso, pero no, aún falta algo muy importante, el archivo sitemap.json 

En este archivo especificaremos las páginas que deseamos crear, los layout templates que queremos usar para cada página, el mapping de nuestro contenido web, los assets y la configuración de los portlets la cual es porporcionada por el tema.

La primera propiedad de nuestro archivo json, es un array con el tipo de páginas que queremos incluir en el importer, publicPages (páginas publicas) ó privatePages (páginas privadas). Posteriormente un objeto que contiene porpiedades que definen el friendlyURL (url amigable), layoutTemplateId (plantilla a utilizar en nuestra página), name (nombre de nuestra página). También contiene un array de columnas (columns) donde añadiremos nuestros portlets (esto no significa que las columnas sean el layout, para eso esta el layoutTemplateId) los cuales son añadidos en la propiedad portletId y portletPreferences, en está ultima propiedad es donde definimos configuraciones para nuestro portlet, por ejempo, si tenemos un Asset Publisher Portlet, este tiene configuraciones como el tipo de ADT con el que va a presentar los articles, configuración de estilos con el portlet decorator, el comportamiento del los links para ver el contenido en otra página o en el mismo portlet entre algunas otras.

Vamos a imaginar que deseamos crear cuatro páginas publicas llamadas home, my workspace, my life, about us, las cuales usarán un layout template de  una columna, y en el que cada página solo contendrá un portlet, en este caso el asset publisher portlet. Para hacer un poco más ulustrativa esta idea, a continuación el sitemap.json.

{
	"publicPages": [
		{
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/home",
			"layoutTemplateId": "1_column",
			"name": "Home"
		},
        {
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/my-workspace",
			"layoutTemplateId": "1_column",
			"name": "My Workspace"
		},
        {
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/my-life",
			"layoutTemplateId": "1_column",
			"name": "My Life"
		},
        {
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/about-us",
			"layoutTemplateId": "1_column",
			"name": "About Us"
		}
	]
}

 

Ya casi terminamos, lo unico que nos falta es decirle a nuestro tema donde deseamos hacer importar recurso al momento de hacer deploy de nuestro tema ya que por defecto, Liferay crea un site template para importar nuestros recursos, pero como le digo eso? bien, cuando construimos un tema usando el theme genarator tool, automáticamente se crea un archivo llamado liferay-plugin-package.properties.

Con las propiedades resources-importer-target-class-name y resources-importer-target-value=[site name] le podemos especificar un site, solo debemos de estar seguros del nombre de site que estamos proporcionando, ya que si nos equivocaramos, podríamos perder contenido que ya tengamos creado en Liferay.

Para nuestro ejemplo, agregaremos esas propiedades de la siguiente manera:

resources-importer-target-class-name=com.liferay.portal.kernel.model.Group
resources-importer-target-value=My New Liferay Site

Ahora solo a hacer el deploy de nuestro tema para importar contenido en nuestro ambiente de Liferay.

Para más información puedes consultar la documentación oficial de resource importer de Liferay.

Integración de Paypal a Laravel 5.2

Paypal es una forma muy popular de pago. La mayoría de las personas la seleccionan porque esta es segura y simple de usar. Si deseas integrar Paypal express checkout a tu aplicación de Laravel 5.2, solamente tienes que seguir los siguientes pasos.

1.- Instalación. En este paso necesitas instalar nestshell/paypal, solo ejecuta el siguiente comando en tu terminal

composer require netshell/paypal dev-master

2.- Añadir las siguientes rutas al archivo de config/app.php.

return [
	......
	$provides => [
		......
		......,
		'Netshell\Paypal\PaypalServiceProvider'
	],
	$aliases => [
		.....
		.....,
		'Paypal' => 'Netshell\Paypal\Facades\Paypal'
	]
]

3.- Credenciales para modo Sandbox y Live de Paypal

En este paso ya debes tener tu client_id y tu código secreto proporcionados por la herramienta sandbox de paypal .  Para ello ya debes contar con una tipo empresarial en Paypal.

Te explico de manera rápida en que parte de Paypal adquieres estos códigos. Si ya hiciste login en tu cuenta tipo developer (sandbox), ve a los siguientes menús del lado izquierdo: My Apps & Credentials , dirígete hacia la opción REST API apps y da clic en el botón Create App.

Aquí están las famosas credenciales 

Ahora que ya tienes creadas tus credenciales, necesitas añadirlas en el archivo de config\services

return [
'paypal' => [
        'client_id' => 'xxx',
        'secret' => 'xxx',
    ],
];

4.- Añadir rutas al archivo de route.php

En este paso vamos a añadir las rutas necesarias para la comunicación en app/http/routes.php

Route::group(['middleware' => ['web']], function () {
 Route::get('payPremium', ['as'=>'payPremium','uses'=>'PaypalController@payPremium']);    
Route::post('getCheckout', ['as'=>'getCheckout','uses'=>'PaypalController@getCheckout']);
    Route::get('getDone', ['as'=>'getDone','uses'=>'PaypalController@getDone']);
    Route::get('getCancel', ['as'=>'getCancel','uses'=>'PaypalController@getCancel']);
});

5.- Ahora que ya creaste las rutas, es necesario generar nuestro Controller

php artisan make:controller PaypalController

Vamos al código fuente de nuestro PaypalController app/http/Controllers/PaypalController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use Paypal;
use App\User;
use Illuminate\Support\Facades\Auth;

class PaypalController extends Controller
{
    private $_apiContext;

    public function __construct()
    {
    	 $this->_apiContext = PayPal::ApiContext(
            config('services.paypal.client_id'),
            config('services.paypal.secret'));

    	//Aquí guarde una configuración para mis credenciales de Sandbox
        /*$this->_apiContext->setConfig(array(
            'mode' => 'sandbox',
            'service.EndPoint' => 'https://api.sandbox.paypal.com',
            'http.ConnectionTimeOut' => 30,
            'log.LogEnabled' => true,
            'log.FileName' => storage_path('logs/paypal.log'),
            'log.LogLevel' => 'FINE'
        ));*/

        //Config live
        $this->_apiContext->setConfig(array(
            'mode' => 'live',
            'service.EndPoint' => 'https://api.paypal.com',
            'http.ConnectionTimeOut' => 30,
            'log.LogEnabled' => true,
            'log.FileName' => storage_path('logs/paypal.log'),
            'log.LogLevel' => 'FINE'
        ));
    }

    public function getCheckout(Request $request)
	{
        $user         = Auth::user(); 
        $id_user      = $user->id;
        $id_purchased_item    = $request->input('id_purchased_item'); //producto que me estan comprando
        $invoice      = $id_user.'-'.$id_purchased_item.'-'.$this->random(5);//generación de invoice aleatorio
        $descripcion  = $request->input('description');
        $quantity     = $request->input('quantity');//cantidad de productos adquiridos
        $total_amount = $request->input('amount');
        $currency     = $request->input('currency_code');//tipo de moneda

	    $payer = PayPal::Payer();
	    $payer->setPaymentMethod('paypal');

	    $item1 = PayPal::item();
        $item1->setName($descripcion)
                ->setDescription($descripcion)
                ->setCurrency($currency)
                ->setQuantity(1)
                ->setPrice($total_amount);

        $itemList = PayPal::itemList();
        $itemList->setItems(array($item1));


        // ### Cantidad
        // Especificando la cantidad del pago
        // Se pueden añadir detalles adicionales como 
        // shipping, tax.
        // Todo estó para que en paypal aparezca desglosado
        // como si de un carrito de compra se tratará
        $amount = PayPal::amount();
        $amount->setCurrency($currency)
            ->setTotal($total_amount);

        // ### Transacción
        // Para quién es el pago y quién lo está pagando. 
        $transaction = PayPal::transaction();
        $transaction->setAmount($amount)
            ->setItemList($itemList)
            ->setDescription("Descripción")
            ->setInvoiceNumber($invoice);

        // ### urls de redirección
        // Rutas a las que será redirigido el comprador después de un pago 
        // aprobado / cancelación

        $redirectUrls = PayPal:: RedirectUrls();
	    $redirectUrls->setReturnUrl(route('getDone'));
	    $redirectUrls->setCancelUrl(route('getCancel'));

        // ### Pago
        // Creamos el pago, para establecer la venta

        $payment = PayPal::Payment();
	    $payment->setIntent('sale');
	    $payment->setPayer($payer);
	    $payment->setRedirectUrls($redirectUrls);
	    $payment->setTransactions(array($transaction));

	    $response = $payment->create($this->_apiContext);
	    $redirectUrl = $response->links[1]->href;
	    
	    return redirect()->to( $redirectUrl );

	}

	public function getDone(Request $request)
	{
        $id               = $request->get('paymentId');
        $token            = $request->get('token');
        $payer_id         = $request->get('PayerID');
        $payment          = PayPal::getById($id, $this->_apiContext);
        $paymentExecution = PayPal::PaymentExecution();

        $paymentExecution->setPayerId($payer_id);
        $executePayment = $payment->execute($paymentExecution, $this->_apiContext);

	    return view("payments.payment-done");
	}


	public function getCancel()
	{
	   return view("payment-cancel", compact("executePayment"));
	}

      /*generación del invoice para paypal */
      public function random($qtd){
 
       $caracteres = 'ABCDEFGHIJKLMOPQRSTUVXWYZ0123456789'; 
       $cantidad_de_caracteres = strlen($caracteres); 
       $cantidad_de_caracteres--; 

       $num_random = NULL; 
       for($x=1;$x<=$qtd;$x++){ 
        $posicion = rand(0,$cantidad_de_caracteres); 
        $num_random .= substr($caracteres,$posicion,1); 
       } 

      return $num_random; 
     } 
}

 

 

 

 

El concepto de namespace en PHP

¿Te acuerdas cuando tu maestra de primaria te decía que tus libretas deberían estar bien ordenadas de acuerdo a la materia de la que trataran?

Pues bien los namespaces son una forma de organización de código que tenemos disponible en PHP; si vienes de un mundo parecido a Java seguramente recuerdas el concepto de paquete o package, que básicamente se encargaba de agrupar varias clases semántica o lógicamente relacionadas bajo un mismo folder (directorio físico o lógico).

Esa misma idea la tenemos en PHP y para usarlos sólo necesitas utilizar la palabra reservada namespace seguido del nombre que desees, por ejemplo si queremos que la clase User sea parte del espacio de nombres App, usaríamos la siguiente sintaxis.

namespace App;

class User {

}

Ventajas de los namespaces de PHP

  • Evitan colisiones entre clases; por ejemplo imagina que estás integrando dos sistemas diferentes bajo una misma aplicación pero ambos sistemas tienen una clase User. Sin usar namespaces no podrías user ambas clases en el mismo archivo PHP.
  • Capacidad de utilizar un alias cuando importas tus clases con el mismo nombre. Es decir, usando namespaces puedes definir un alias fácilmente como en el siguiente ejemplo:
namespace App;

use Webtraining\Auth\Models\User as WebtrainingUser;

class User {
    public function copyUser(WebtrainingUser $user) {
       // Some logic goes here!
    }
}

Para más información sobre aliasing/importing visita este artículo Using namespaces: Aliasing/Importing

Y aquí tienes el Namespaces overview de la documentación oficial.

Que tengas un excelente día.

Iterar campos repetibles en un Application Display Template de Liferay 7

Una de las cosas más complicadas de comprender es cómo usar los llamados repeatable fields en un Application Display Template (ADT) en Liferay 7; debido a que no disponemos de tanta documentación oficial. Por tal motivo quisiera compartir contigo un ejemplo de una ADT para el famoso Asset Publisher; comencemos:

  1. Imagina que tienes una Estructura (Structure) con un campo de tipo “Documents and Library” llamado attachment el cual ha sido configurado como repeatable=true.
  2. Ahora bien, necesitas crear un listado de todos los attachments y obtener tanto el link al documento, como su etiqueta (label).
<#if entries?has_content>
    <!-- START: item loop -->
    <div class="container">
        <div class="row">
            <#list entries as entry>
                <#assign renderer = entry.getAssetRenderer()/>
                <#assign className = renderer.getClassName() />
                <#if className == "com.liferay.journal.model.JournalArticle">
                    <#assign journalArticle = renderer.getArticle() />
                    <#assign document = saxReaderUtil.read(journalArticle.getContent()) />
                    <#assign allAttachments = document.selectNodes( "//dynamic-element[@name='attachment']") />
                    <!-- START: item html -->
                    <div class="col-xs-12 col-sm-12 col-md-6 col-lg-6">
                        <div class="media card-file">
                            <div class="media-body">
                                <#list allAttachments.iterator() as attachment>

                                    <#if attachment.valueOf( "dynamic-element[@name='fileLabel']")??>
                                        <#assign fileLabel = attachment.valueOf( "dynamic-element[@name='fileLabel']") />
                                        <#else>
                                            <#assign fileLabel="Download File" />
                                    </#if>

                                    <#if attachment.valueOf( "dynamic-content")??>
                                        <#assign fileLink = attachment.valueOf( "dynamic-content") />
                                        <#else>
                                            <#assign fileLink="#" />
                                    </#if>

                                    <div class="card-file__attachment">
                                        <a href="${fileLink?trim}" target="_blank">${fileLabel?trim}</a>
                                    </div>
                                </#list>
                            </div>
                        </div>
                    </div>
                    <!-- END: item html -->
                </#if>
            </#list>
        </div>
    </div>
    <!-- END: item loop -->
</#if>

Lo más importante en el código de Freemarker anterior es que estamos utilizando una query de XPath para obtener todos los nodos del XML que representa nuestro Journal Article:

<#assign allAttachments = document.selectNodes( "//dynamic-element[@name='attachment']") />

Acto seguido procedemos a iterar ese NodeList, usando la instrucción #list de Freemarker:

<#list allAttachments.iterator() as attachment>
// Do something here :)
</#list>

Tip. Si tienes dudas qué es una ADT; esta es la definición oficial de la documentación de Liferay:

The application display template (ADT) framework allows Liferay administrators to override the default display templates, removing limitations to the way your site’s content is displayed. With ADTs, you can define custom display templates used to render asset-centric applications. For example, you may want to show blog entries horizontally instead of vertically, or list your assets in the asset publisher application in different sizes.
https://dev.liferay.com/discover/portal/-/knowledge_base/6-2/using-application-display-templates

Compartiendo información entre componentes con ReactiveX en Angular 4

El problema

Una de las ventajas de usar Angular llega a ser a su vez un lío al momento de compartir información entre componentes no relacionados, es decir para pasar datos de un componente padre a un componente hijo tenemos el famoso @Input y para compartir información de un hijo a un padre podemos usar un @Output que al final del día es un EventEmitter. Pero, ¿qué sucede cuando queremos usar la información de un componente que no está relacionado directamente? Esto es útil cuando nuestros datos (variable data) se encuentren en algún componente, pero nosotros requerimos esa información en un componente muy inferior o que no tiene relación directa. Podríamos tener una cadena de @Input tras @Input hasta llegar al componente que requiere la información, pero esto sólo funcionaría si estos componentes estuvieran en forma de cascada uno dentro de otro como podemos ver en esta ilustración:

 

Pero además de que en algún momento ese código podría ser poco sostenible, tenemos el problema de que al usar estos inputs en ocasiones no se lleva a cabo de manera correcta el change detection de Angular, por lo que podríamos tener que llenar nuestra aplicación de timeouts y eso no es correcto (o Zones que dan un mecanismo más avanzado para este tipo de situaciones).

La solución

Hoy vamos a usar un operador que nos provee la librería de ReactiveX, que junto con Angular nos facilita mucho el poder compartir información entre componentes.

En este ejemplo compartiremos un arreglo de números.

Lo primero que vamos a hacer es crear un servicio e importar el operador BehaviorSubject.

import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable()
export class SharingDataService {
data: Array<any> = [];

y creamos una variable la cual será nuestra manera de comunicarnos con los demás componentes o servicios que requieran la información.

Primero asignados a una variable un new BehaviorSubject<>() para posteriormente asignar esta variable como un Observable.

public _dataSource = new BehaviorSubject<Array<any>>([]);
dataSource$ = this._dataSource.asObservable();

La variable _dataSource la usaremos para poder indicarle a todos los elementos suscritos a nuestro observable que tenemos información y nuestra otra variable dataSource$ será con la cual nosotros podremos suscribirnos a la información.

pongamos un ejemplo

Ahora cómo podemos hacer que el componente principal transmita la información a los demás? pues crearemos una función para esto dentro de nuestro servicio, algo como esto:

public setData(data:Array<any>){
   this.data = data;
   this._dataSource.next(data);
}

Podemos ver que estamos usando la variable _dataSource que es nuestro BehaviorSubject con un .next esto lo que nos permite es alertar a todos los suscriptores que esperan por información pasando como parámetro la información que queremos transmitir.

Ahora viene la otra parte, ¿como consumo la información que el BehaviorSubject está transmitiendo? bueno para esto dentro de nuestro componente importamos otro operador de ReactiveX llamado ‘Subscription’ y el servicio que creamos antes.

import {Subscription} from "rxjs/Subscription";
import {SharingDataService} from "./sharingDataService";

y declaramos nuestra variable del mismo tipo, y nuestro servicio en el constructor:

dataSubscription: Subscription;
data: Array<number>

constructor(public _sharingDataService:SharingDataService){
}

y dentro de nuestro ngOnInit realizamos la suscripción a la variable a la cual asignamos el BehaviorSubject como Observable.

public ngOnInit() {
    this.dataSubscription = this._sharingDataService.dataSource$
    .subscribe(data => {
        this.data = data;
     });
}

y con esto nuestros componentes podrán compartir la información con cualquier otro componente o servicio que este suscrito a nuestro BehaviorSubject.

solo no olvides desuscribirte cuando destruyas el componente ya que consumiría recursos y no los estaríamos usando.

public ngOnDestroy(): void {
    this.dataSubscription.unsubscribe();
}

Comandos más usados de Laravel PHP Artisan

Cuando nos encontramos trabajando con Laravel una de las mejores herramientas que tenemos a nuestra disposición es el generador llamado artisan, aquí te dejamos nuestra lista de los comandos que más usamos en el día a día:

Crear una tabla

# Crear la tabla "posts"
php artisan make:migration create_posts_table --create posts 
php artisan migrate

Modificar una tabla existente

# Agregar el campo "summary" a la tabla "posts"
php artisan make:migration add_summary_to_posts_table --table="posts"

# Agregar varios campos nuevos a la tabla "users"
php artisan make:migration add_fields_to_users_table --table="users"

php artisan migrate

Generar una llave para hashing

php artisan key:generate

Crear un middleware

# Crear un middleware para verificiar si el usuario tiene una sesión
php artisan make:middleware HasUserASession

Crear un modelo

# Crear modelo Post
php artisan make:model Post

Crear un controlador

# Crear el controlador PostsController compatible con REST (resource) 
php artisan make:controller PostsController --resource

Generar código boilerplate para autenticación de usuarios

Este comando debería ejecutarse lo más pronto al iniciar tu proyecto ya que re-escribirá tus layouts y controladores de Autenticación y Usuarios

php artisan make:auth