webs

Symfony: creando landings con formularios, segunda parte

2018-05-08 - Categorías: PHP / Symfony
Landing pages con Symfony

En el post anterior escribí sobre cómo crear una landing page rápido con Symfony 4, y se me quedó pendiente el enviar por email los mensajes del formulario. Es decir, ya tenemos en un rato una rudimentaria ‘landing page’ donde un maquetador o el equipo de marketing disfrutará poniendo cualquier cosa. Será como maquetar con HTML, CSS y Javascript sin tener ningún engorroso CMS por debajo limitándonos lo que podemos hacer. Además tendremos la base de un buen Symfony para después añadir lo que queramos.

Ya tenemos la captación de contactos (o capture leads para los entendidos), que se guardan en una base de datos embebida en la propia web. Sin complicadas instalaciones ni configuraciones SQL, directamente con una base de datos SQLite embebida.

Sólo queda entonces hacer 3 cosas:

  • Instalar el módulo de Swiftmailer.
  • Configurarlo.
  • Y poner la acción de enviar el email.

Vamos al grano..

1. Instalando Swiftmailer

Para esto basta con poner en el terminal lo siguiente:

composer require symfony/swiftmailer-bundle

..esperamos, y si todo ha ido bien tenemos que ver en el terminal algo como lo siguente:

Using version ^3.2 for symfony/swiftmailer-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
 - Installing egulias/email-validator (2.1.4): Loading from cache
 - Installing swiftmailer/swiftmailer (v6.0.2): Loading from cache
 - Installing symfony/swiftmailer-bundle (v3.2.2): Loading from cache
Writing lock file
Generating autoload files
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
Symfony operations: 1 recipe (8c3c4103f30ebcc7579093a6f39dc028)
 - Configuring symfony/swiftmailer-bundle (>=2.5): From github.com/symfony/recipes:master
Executing script cache:clear [OK]
Executing script assets:install --symlink --relative public [OK]

Some files may have been created or updated to configure your new packages.
Please review, edit and commit them: these files are yours.

2. Configurar swiftmailer

Ahora las principales variables de configuración son variables de entorno. Esto es mucho más seguro ya que se pueden incluso no guardar en ningún fichero, pero la forma más sencilla es usar el fichero .env que debemos tener en el directorio raiz del proyecto. Si lo abrimos veremos que tenemos añadida la configuración de Swiftmailer:

###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:password@localhost"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###

Como bien explica en los comentarios, así hacemos. Pero para evitarte problemas te recomiendo usar siempre SMTP con un buen servidor de correo electrónico. Por Internet hay muchos ejemplos, uno puede ser este:

MAILER_URL=smtp://tudominio.com:587?encryption=tls&auth_mode=login&username=tuusuario&password=tucontraseña

Estas configuraciones usando SMTP son compatibles con la mayoría de los servidores de correo electrónico, sino todos. Tales como Mandrill, Amazon SES, Gmail, etcétera.. podrás configurarlos así, sólo tienes que encontrar las configuraciones que le pondrías a tu Outlook o Thunderbird y ponerlas en el proyecto de Symfony.

3. Enviar el email

Remitiéndome al post anterior, sólo nos queda en el controlador donde recibimos el contenido del formulario el enviarnos un email informándonos de que alguien nos ha dejado un mensaje. Para esto ponemos algo como esto en el controlador, marco en negrita lo nuevo:

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use App\Form\ContactType;
use App\Entity\Contact;

class MainController extends Controller
{
    /**
     * @Route("/", name="main")
     */
    public function index(Request $request, \Swift_Mailer $mailer)
    {
        $contact = new Contact();
        $theForm = $this->createForm(ContactType::class, $contact);

        $theForm->handleRequest($request);

        if ($theForm->isSubmitted() && $theForm->isValid()) {
            // save in database
            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($contact);
            $entityManager->flush();

            // send email
            $message = (new \Swift_Message('JnjSite.com:: mensaje desde la web'))
                ->setFrom(['info@jnjsite.com' => 'Web JnjSite.com'])
                ->setTo([
                    'info@jnjsite.com', ])
                ->setBody(
                    $this->renderView(
                        'main/email.html.twig',
                        array('data' => $theForm->getData())
                ),
                'text/html'
            );
            if ($mailer->send($message)) {
                // Email enviado correctamente, vamos a limpiar el formulario.
                $theForm = $this->createForm(ContactType::class);
            } else {
                // Aquí podemos hacer algo más porque no se han enviado bien el email.
            }
        }

        return $this->render('main/index.html.twig', [
            'theForm' => $theForm->createView(),
        ]);
    }
}

Verás que no hay que complicarse mucho. Y por último necesitamos también la plantilla del correo electrónico, que la he puesto en templates/main/email.html.twig:

¡Hola!<br>
Hemos recibido un mensaje en el formulario.<br>
<br>
Nombre: <strong>{{ data.name }}</strong><br>
Email: <strong>{{ data.email }}</strong><br>
Asunto: <strong>{{ data.subject }}</strong><br>
Mensaje:<br>
<strong>{{ data.message }}</strong><br>

Terminando

Si todo ha ido bien, ahora podemos probar el formulario arrancando el proyecto en local, si ponemos esto en el terminal:

php bin/console server:start

Y visitamos http://localhost:8000/ nos enviamos un formulario y debemos de ver algo como esto:

Symfony Tutorial 2 enviando mail formulario

..sino me he explicado bien, o quieres decir algo, no dudes en dejar un comentario aquí abajo 😉


Symfony: creando landings con formularios

2018-04-21 - Categorías: PHP / Symfony
Landing pages con Symfony

Traigo hoy un HOWTO para hacer landing pages sobre Symfony 4 orientadas a captación de clientes. Un code-kata con el que en menos de 1 hora tendremos la estructura básica funcional sobre Symfony 4 para recibir contactos, guardarlos en base de datos y enviárnoslos por email.

Llevo un par de semanas bien ajetreadas: migrando un proyecto desde Symfony 3 al 4, otro desde un Joomla en mal estado a Symfony 4, otro proyecto terminándolo de poner en marcha con Symfony 4. Pequeños proyectos de poco tiempo que me han dado muchas ideas para escribir. Así que, cargado de ideas, sigo poco a poco redescubriendo Symfony con Symfony 4. Y me sigo sintiendo como un niño con juguete nuevo 😀

Tengo que decir que Symfony 4 ha mejorado mucho en simplicidad. No hay que saberse cómo funciona todo el conjunto con los 50-60 componentes, ni gran parte de ellos, para comenzar a hacer aplicaciones web robustas: https://symfony.com/components Ni tampoco hay que hacer complicados tutoriales muy largos antes de comenzar a construir con Symfony. Eso sí, tenemos que tener claro cómo funciona la línea de comandos con PHP, y tener muy claro cómo funciona Composer, esto ya lo expliqué en el primer tutorial sobre Symfony 4. Si todavía no te manejas bien con Composer y la línea de comandos.. ¡será mejor que no sigas!

Vayamos al grano..

Comenzando

Pasando de la teoría, y partiendo de que ya hamos visto los posts anteriores, vamos a seguir los siguientes pasos:

  1. Preparar los componentes que necesitamos.
  2. Preparar la base de datos que va a almacenar los contactos.
  3. Pintar el formulario en la página de aterrizaje, o landing page para los amigos.
  4. Recibir el formulario.
  5. Guardar en base de datos.
  6. Y enviarlo a nuestro email.

Comencemos..

1. Preparando los componentes

Creamos el esqueleto del proyecto, entramos al directorio y ponemos los componentes que vamos a usar:

composer create-project symfony/skeleton symfony-tutorial-2
cd symfony-tutorial-2
composer require twig
composer require form
composer require security
composer require doctrine
composer require maker
composer require server

2. Preparando la base de datos

Para simplificar el proceso, y no tener que instalarnos localmente un motor de base de datos completo propongo usar SQLite. SQLite es un tipo de base de datos embebida en la aplicación. Esto quiere decir que no necesita de instalación, simplemente la aplicación creará un fichero y manejará los datos sin ningún otro programa intermediario.

Primero vamos a crear la entidad Contact, que va a ser un objeto de PHP que va a almacenar los datos que un visitante nos deja en el formulario. A su vez, esta entidad se reutiliza para guardar automáticamente en la base de datos cada registro en una tabla que se creará automáticamente a partir de la entidad Contact. También se reutilizará para crear automáticamente el formulario. Esto de que Symfony genera automáticamente los códigos fuentes es brillante. Ejecutemos:

php bin/console make:entity

Y seguimos las instrucciones, vamos a crear los campos clásicos: nombre, email, asunto y mensaje.

Symfony Tutorial 2 landings Entity

Si te equivocas creando los campos no te preocupes, borra los ficheros y vuelve a empezar. Es la única forma de hacerlo y de aprender. Además, este comando make:entity te permite también añadir campos nuevos sobre la marcha si es que la entidad ya existe.

Ahora tenemos que decirle a la aplicación web que guarde los datos en SQLite, ya que por defecto los tratará de guardar en Mysql. Así que vamos a nuestro fichero .env y ponemos:

#DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db

Ahora las principales variables de configuración como contraseñas, parámetros de configuración, etc.. entran a la aplicación a modo de variables de entorno. Esto es más seguro y práctico, pero también es material para otro post. Ya está, ahora ejecutamos esto:

php bin/console doctrine:database:create
php bin/console doctrine:schema:create

Si todo ha ido bien, acabamos de crear el fichero data.db y la tabla Contact para guardar los contactos que nos hagan los visitantes.

3. Pintar el formulario en la página de aterrizaje

Vamos a simplificar todo, así que sólo tendremos una página que va a ser la home, en donde estará este formulario. Así creamos el controlador que recibe la visita:

php bin/console make:controller

Yo le he llamado MainController al mío y me crea los ficheros necesarios:

Symfony tutorial 2 make controller

De nuevo Symfony nos ha hecho mucho trabajo. Podemos ya arrancar el servidor de desarrollo haciendo esto:

php bin/console server:start

Y vamos a ver los resultados en http://localhost:8000/

Para poner el formulario aquí vamos a editar el fichero symfony-tutorial-2/templates/main/index.html.twig y ponemos:

{% extends 'base.html.twig' %}

{% block title %}¡Creando landings!{% endblock %}

{% block body %}
    {{ form(theForm) }}
{% endblock %}

Ahora mismo la plantilla intentará pintar el formulario llamado theForm pero no se lo hemos pasado, vamos a crearlo:

php bin/console make:form
Symfony tutorial 2 formulario

Mira en la imagen, si le decimos a Symfony al crear el formulario que cargue de una entidad que ya hemos creado, Symfony creará todos los campos del formulario automáticamente. Así que ahora queda pasar con el controlador el formulario a la plantilla Twig. Editamos el fichero symfony-tutorial-2/src/Controller/MainController.php para que quede como lo siguiente:

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use App\Form\ContactType;

class MainController extends Controller
{
    /**
     * @Route("/", name="main")
     */
    public function index()
    {
        $theForm = $this->createForm(ContactType::class);

        return $this->render('main/index.html.twig', [
            'theForm' => $theForm->createView(),
        ]);
    }
}

Ya podemos recargar la página en el navegador y debemos ver el formulario. Sino, algo nos falta. Ahora nos queda recibir los formularios, guardarlos en base de datos y enviarnos estos datos a nuestro email.

4. Recibir el formulario

Si nos fijamos en el formulario, todavía no tiene botón de enviar, así que vamos a editar el fichero del formulario para añadírselo. En el fichero symfony-tutorial-2/src/Form/ContactType.php añadimos esta línea en los campos:

->add('submit', SubmitType::Class)

..y en la cabecera incluimos la clase del tipo submit así:

use Symfony\Component\Form\Extension\Core\Type\SubmitType;

Refrescamos y nos sale el formulario listo para enviar los datos:

Symfony tutorial 2 formulario submit

Ahora vamos a recibir los datos en el controlador. Para esto tenemos que recibir los datos en un objeto de PHP, del tipo de objeto que hemos creado antes. Es decir, vamos a usar la entidad Contact para que Symfony se encargue de todo. Es más fácil leer el código que explicarlo, en el controlador ponemos ahora:

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use App\Form\ContactType;
use App\Entity\Contact;
use Symfony\Component\HttpFoundation\Request;

class MainController extends Controller
{
    /**
     * @Route("/", name="main")
     */
    public function index(Request $request)
    {
        $contact = new Contact();
        $theForm = $this->createForm(ContactType::class, $contact);

        $theForm->handleRequest($request);

        if ($theForm->isSubmitted() && $theForm->isValid()) {
            // save in database
            // send email
        }

        return $this->render('main/index.html.twig', [
            'theForm' => $theForm->createView(),
        ]);
    }
}

He marcado en negrita lo que acabo de añadir. Con esto se recibe y se valida que los datos son correctos automáticamente. Es una maravilla, ¡Symfony se encarga de casi todo! Ya sólo nos queda guardar el contacto en la base de datos y enviárnoslo por email.

5. Guardar en la base de datos

Para guardarlo en la base de datos simplemente añadimos lo siguiente:

// save in database
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($contact);
$entityManager->flush();

Ahora sí, probamos el formulario y nos lo enviamos. Tendremos que poder abrir el fichero de la base de datos para ver cómo se guardan estos datos en la tabla que corresponde. Tengo Sqliteman instalado para ver estos ficheros, que viene en los repositorios de Ubuntu, pero hay muchos gestores de SQLite. Con un gestor de ficheros SQLite podremos abrir el fichero symfony-tutorial-2/var/data.db y veremos algo parecido a esto:

Symfony tutorial 2 formulario recibiendo datos

Ya sólo nos quedaría enviárnoslo a nuestro email. Es sencillo pero vamos a dejarlo para el siguiente post, que este ya está quedando muy espeso. Además el envío de correos electrónicos tiene mucho juego y hoy no me queda más tiempo 🙂

¡Un saludo!


Magento 1: resolviendo conflictos entre módulos

2018-04-07 - Categorías: Magento / PHP
Magento conflictos entre modulos

Magento es muy grande, aguanta carros y carretas, pudiéndole añadir gran cantidad de módulos. Uno de los proyectos en los que he participado alcanzó la friolera de 131 módulos, siendo algunos de estos módulos mega-módulos. Ha requerido mucho esfuerzo, todo hay que decirlo. Casi nunca suele ser «copiar y pegar los ficheros y ya está» como aquel que dice. Por el camino hay que resolver los conflictos, y tratamos día a día de evitar el añadir módulos si no es bien necesario. Hacemos limpieza de todos los que no se usen, y tratamos de actualizar todos los que se puedan, depende del tiempo que haya. Siempre surgen conflictos, y supone mucho tiempo hacer funcionar todas las opciones de ciertos módulos que entran en conflicto entre sí. Esto es el día a día del mantenimiento de los Magentos.

Origen de los problemas

Los desarrolladores hacen los módulos en un Magento limpio, recién instalado, sin que se requiera ningún otro módulo para su funcionamiento. Este es el entorno ideal, un Magento recién instalado. Pero la realidad nunca es así.

Continuar leyendo..

Magento 1: flushear la caché de Magento

2018-03-27 - Categorías: Magento / PHP
Flus Magento Cache

Es habitual que cuando haces cambios en Magento necesites flushear la caché. Esto es resetear, borrar los ficheros o datos temporales, que almacena Magento para agilizar sus tareas.

Magento va guardando ficheros intermedios que se ejecutan más rápido que los originales. También guarda variables, combina ficheros de configuración de los módulos, traducciones, bloques, layouts de las plantillas, etc.. Todos estos ficheros agilizan mucho la ejecución, así que si no tienes la caché activada deberias de activarla ya mismo.

Problema

Puede ser que tengas que estar instalando y desinstalando módulos y de repente deja de funcionar Magento. Quizá has modificado en la base de datos algún dato y no se ven los cambios. O quizá incluso modificando algunos ficheros te deja de funcionar Magento.

El problema viene cuando no puedes entrar al panel de control de Magento y darle al botón de ‘Flush Magento Cache’.

Continuar leyendo..

Magento 1: listar todas las alertas por vuelta a stock de producto

2018-03-07 - Categorías: Magento / PHP

En el post anterior hablaba de cómo activar estas alertas, para que el cliente se pudiera registrar a las alertas por vuelta a stock de producto. Si tenemos activada esta opción deben de haberse apuntado los clientes interesados en ciertos productos que no tienes en stock ahora mismo.

El siguiente paso es pedir productos a los proveedores..

Ahora bien, según vuelvas a tener stock, si había algún suscrito a esta alerta, recibirá un email invitándole a venir a comprar el producto. Pero y si además de esto, ¿miras qué suscripciones y a qué productos se han suscrito para hacer las compras más a tiro hecho?

Continuar leyendo..

Magento 1: dejar que los clientes se suscriban a alertas de vuelta a stock

2018-03-05 - Categorías: Magento

Una opción muy interesante para tener activada en Magento es el dejar que los clientes se puedan suscribir a ciertas alertas por email. Estoy hablando de que si un producto se nos queda sin stock, que se muestre en el front y que el cliente pueda suscribirse para recibir una alerta cuando volvamos a tenerlo. Estas son las alertas por vuelta a stock de producto.

Si tenemos los productos configurados para mostrarse en el front cuando están fuera de stock, podemos también dejar que el cliente pueda registrarse para recibir una alerta. Entonces, si el cliente se registra en esta alerta, cuando pongamos en stock el producto, recibirá un sencillo email explicándole que tiene el producto disponible para comprar.

Mostrar productos fuera de stock o no en el front

En Sistema > Configuración > Catálogo > Inventario > Opciones de existencias > Visualizar los productos agotados tenemos que ponerlo a Sí. De esta manera se verán en el front.

Magento inventario stock ver productos agotados

Activar las alertas de vuelta a stock de productos

En Sistema > Gestión > Catálogo > Catálogo > Alertas de producto > Avisar cuando el producto vuelva a estar en existencias lo tenemos que poner también a Sí.

Magento alertas de vuelta a stock de producto

Terminando

Con esto ya tendremos en el front un mensaje que se debe mostrar automáticamente en el front cuando el producto se ha quedado fuera de existencias. Legalmente, para hacer lo correcto, el cliente será dirigido al registro normal de usuario, en donde al registrarse aceptando las condiciones de uso normales también será suscrito al producto en cuestión.

Si el usuario ya ha hecho login, al hacer click en suscribirse a la vuelta a stock automáticamente hará esta suscripción porque ya está identificado.

Magento ficha de producto alertas por vuelta a stock

En el back podremos ver en la ficha de producto las alertas por vuelta a stock que tiene cada producto. Automáticamente Magento se encargará de notificarle al cliente que ya tenemos de vuelta el producto, con la plantilla de email que hayamos configurado arriba.

Otro tema que dejo pendiente para otro post, es que antes de hacer el siguiente pedidos a proveedores, sería interesante sacar el listado completo de todas las suscripciones a vuelta a stock de productos que tengamos. Así podremos ver qué productos, y cuántos clientes se han suscrito. Podremos lanzar acciones comerciales con dichos productos a estos clientes, o simplemente pedir esos productos esperando que cuando los tengamos se les puedan vender a estos clientes..


Magento 1: listar clientes con más actividad en los últimos meses

2018-02-26 - Categorías: Magento
eCommerce clientes y ventas

Una información muy interesante para el equipo directivo de una empresa, puede ser simplemente el saber qué clientes son los más activos. Quizá queremos calcular un promedio de pedidos por cliente, cuántos intentos de pedido ha habido sin llegar a éxito, terminados en cancelación o no. Quizá sólo hacen un pedido y no vuelven más a la web. O quizá no suelen hacer ni un pedido, pero los que hacen pedidos suelen repetir…

Esta información, y mucha más, es información necesaria para lo llamado como: Inteligencia de Negocio. Esta información, muchas veces no es suficiente con los reportes del panel del control, pero es bastante sencillo de sacar a partir de scripts en PHP.

En el panel de control de Magento, podemos ir a la sección de Informes > Clientes > Clientes por total de pedidos, o a Informes > Clientes > Clientes por número de pedidos y veremos listados exportables como el siguiente:

Los subreportes en Magento de casa están limitados a 5, por esto que sólo verás 5 resultados por día o por mes. Lo que no veo, por ejemplo, es que no salen los clientes con 0 pedidos, tampoco tengo emails ni teléfonos para arrancar acciones comerciales. Esto es un problema, porque aunque puedo ver cierta información, y la puedo exportar a CSV, no tengo casi información reutilizable.

Así que aquí abajo dejo el script que saca el 100% de los resultados. Editarlo para añadir la información de los clientes y sus pedidos no es complicado.

Listando clientes

Vamos con el simple caso de sacar un listado de los clientes y su número de pedidos. Primero que todo necesitaremos los clientes, vamos a sacar sólo los creados este año:

$customers = Mage::getModel('customer/customer')->getCollection()
    ->addFieldToFilter('created_at', array(
        'from' => '2018-01-01 00:00:00',
    ));

Dentro de $customers tendremos un array de clientes. Luego vamos a necesitar los pedidos para cada uno de ellos.

Listando pedidos

Así tenemos los pedidos de un sólo cliente desde una fecha:

$orderCollection = Mage::getResourceModel('sales/order_collection')
    ->addFieldToFilter('customer_id', $customerId)
    ->addFieldToFilter('created_at', array(
        'from' => '2018-01-01 00:00:00',
    ))
    ->setOrder('created_at', 'desc');

..donde $customerId es el identificador del cliente dentro de Magento.

Todo junto

Juntando todo en un sólo script nos puede quedar algo así:

<?php

require_once __DIR__.'/app/Mage.php';

Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);

$customers = Mage::getModel('customer/customer')->getCollection()
    ->addFieldToFilter('created_at', array(
        'from' => '2018-01-01 00:00:00',
    ));

echo 'ID,TAXVAT,EMAIL,NOMBRE,APELLIDOS,PEDIDOS'.PHP_EOL;

foreach ($customers as $customer) {
    $customer = Mage::getModel('customer/customer')->load($customer->getId());

    echo $customer->getId().','
        .trim($customer->getTaxvat()).','
        .trim($customer->getEmail()).',"'
        .trim($customer->getFirstname()).'","'
        .trim($customer->getLastname()).'",';

    $orderCollection = Mage::getResourceModel('sales/order_collection')
        ->addFieldToFilter('customer_id', $customer->getId())
        ->addFieldToFilter('created_at', array(
            'from' => '2018-01-01 00:00:00',
        ))
        ->setOrder('created_at', 'desc');

    echo $orderCollection->count();

    echo PHP_EOL;
}

Este script lo podemos meter dentro de un fichero .php, por ejemplo llamado customersAndOrders.php, y lo ponemos en el directorio raíz del proyecto. Ahora lo ejecutamos desde línea de comandos:

$ php customersAndOrders.php

Y deberemos de ver un listado de estos clientes en formato .csv

ID,TAXVAT,EMAIL,NOMBRE,APELLIDOS,PEDIDOS
76022,1231321E,afasf.asfafsaf@gdfgfdg.com,"Nombre1","Apellidos1",1
76023,,asdasd@dfgdfg.com,"Nombre2","Apellidos2",3
76025,54566654F,sadsadasd@dfgdfg.com,"Nombre3","Apellidos3",1

Que de igual manera podemos guardarlo a un fichero .csv para luego estudiarlo en LibreOffice o similar como un Excel, así:

$ php customersAndOrders.php > customersAndOrders.csv

¿Sencilllo verdad? Espero que sirva.

Un saludo.


GNU/Linux: bloqueando visitas por país

2018-02-21 - Categorías: GNU/Linux
Internet Firewall

Siguiendo con el post anterior de bloqueo de IPs usando el cortafuegos integrado en el núcleo de Linux, llegamos a la necesidad de por ejemplo bloquear a cierto país. Hay negocios, o páginas web, que han recibido quizás ataques automáticos desde IPs que residen en Rusia, Rumanía o China. Si cerramos dicha entrada al servidor nos evitaríamos muchos problemas.

Tengamos en cuenta que si nuestro servidor trabaja con servidores que residen en estos países, podremos encontrarnos con conexiones que no se hacen, no dan respuesta al conectar, denegación de conexión.. Es complicado si nos olvidamos de que hemos bloqueado, por ejemplo, a Rusia. Si luego resulta que contratamos un servicio ruso, cuyos servidores residen en Rusia, estaremos contratando un servicio que no nos va a poder conectar.

Obteniendo IPs registradas por país

El primera paso es tener un listado de IPs del país en concreto que queremos bloquear. Para esto tenemos varias páginas que nos los sirven sin cargo. Por ejemplo:

http://www.ip2location.com/free/visitor-blocker
https://www.countryipblocks.net/country_selection.php
https://www.nirsoft.net/countryip/
http://www.ipdeny.com/ipblocks/
..

Mejor coge los rangos de IPs del país que quieras bloquear usando el formato en CIDR. Es decir de la forma red/máscara, son así:

2.136.0.0/13
2.152.0.0/14

Tienes entonces que guardarte este listado de CIDRs de forma que tengas 1 CIDR por línea del fichero. Lo guardas en un fichero de texto, y se lo subes al servidor.

Metiendo el bloqueo en el firewall de Iptables

Para esto, desde un servidor GNU/Linux, bastará con ejecutar lo siguiente:

$ while read line; do sudo ufw deny from $line; done < IPsCountryCIDR.txt

Ahora hay que esperar mientras que se cargan todos los bloqueos en el cortafuegos. Tardará un buen rato porque UFW se encarga de transformar, línea a línea, al formato de Iptables.

Sólo nos quedará comprobar que está activo y con las reglas de bloqueo funcionando. Simulando una visita desde dicho país podremos comprobarlo.

Simulando visita desde cierto país

Para esto puedes usar el TorBrowser, del enrutamiento cebolla. Puedes descargártelo aquí:

https://www.torproject.org/

El navegador de la red Tor es un proyecto, que usando Firefox, hace que podamos navegar anónimamente. Esta anonimización la hace creando un camino de conexiones de varios nodos, desde nuestro PC, a la página destino. De esta manera podemos decirle al TorBrowser que queremos que el último nodo sea en un país concreto. Para esto abrimos el fichero este:

Browser/TorBrowser/Data/Tor/torrc

..y ponemos lo siguiente:

ExitNodes {es} 
StrictNodes 1

El código entre llaves es el código del nodo final. Con esta configuración podemos visitar y ver esto en el navegador:

Tor navegando desde España

Terminando

Esto es todo, si has seguido todos los pasos, debes de haber podido bloquear el acceso a cierto país y comprobarlo con el TorBrowser.


GNU/Linux: automatizar backups de Mariadb/Mysql

2018-02-16 - Categorías: GNU/Linux
Historial

El software de gestión de servidores te puede automatizar esta y otras tareas. Pero si estas haciendo artesanalmente un servidor, para no gastar ni un bit innecesariamente en correr software de gestión de servidores, tendrás que automatizar esta tarea: el hacer copias de seguridad de la base de datos.

Sí, más vale prevenir que curar. Da por hecho que tarde o temprano los sistemas se rompen, así que más te vale haberte prevenido y tener una copia de seguridad por si acaso. Como me ha llevado un rato buscar entre los foros para construir esto, aquí que lo dejo por si a algún ubuntero le sirve.

Creando el script

Le idea es crear un sencillo Shell Script que automatice la copia de seguridad. Así que creo el fichero /home/ubuntu/backup.db.sh siguiente:

#!/bin/bash
mysqldump -h localhost -u usuario -pcontraseña nombre_base_de_datos | gzip > ~/backups/backup.db.$(date +%Y%m%d).sql.gz
find /home/ubuntu/backups/ -type f -mtime +30 -name 'backup.db.*.gz' -execdir rm {} \;

Este script hace 2 cosas. La primera es sacar una copia de la BD y la comprime a un fichero .gz. Lo segundo es que revisa si hay ficheros .gz de más de 30 días en el directorio de copias de seguridad, borrándolos.

Creando carpeta que almacenará los ficheros

De esta manera necesitaremos crear el directorio para alojar las copias de seguridad:

$ mkdir /home/ubuntu/backups/

Permisos de ejecución del script

Para poder ejecutar este script tendremos que darle permisos de ejecución así:

$ chmod +x /home/ubuntu/backup.db.sh

Ejecutando el script cada día

Sólo falta ejecutar este script cada día. Para esto ejecutamos desde el usuario ubuntu lo siguiente:

$ crontab -e

Nos saldrá una pantalla en donde podemos editar cada tarea programada. Cada línea de este fichero es una tarea programada. Así que tenemos que añadir aquí esta línea:

0 2 * * * /home/ubuntu/backup.db.sh > backup.db.lastLog

Guardamos y salimos del editor. Con esto ya se ejecutará dicho Shell Script cada noche a las 2:00 AM, dejando registro en el fichero backup.db.lastLog.

Terminando

Sólo me queda añadir que quizá quieres añadir a lo anterior una copia de ficheros. Para esto no tienes más que añadir en el script las líneas que te hagan dicha copia de ficheros. Puedes probar el script en cualquier momento así desde línea de comandos, como si fuera un .bat:

$ /home/ubuntu/backup.db.sh

Espero que sirva.
Un saludo.


Magento 1: cómo sacar las ventas por método de pago

2018-01-26 - Categorías: Magento / PHP
Magento ventas por mes y método de pago

Hoy traigo otro code-kata, esta vez para Magento y hecho en PHP. Se trata de un simple script que se lanza en línea de comandos. Con este script obtendremos primero todos los métodos de pago que se han usado al hacer los pedidos. Después, haremos un listado, por método de pago, y entre ciertas fechas, del monto de pedidos completados por método de pago.

Es decir, supongamos que queremos saber cómo han ido las ventas con el método de pago por tarjeta de crédito. O quizá queremos saber si la financiación es un método de pago que funciona bien. O quizá simplemente queremos tener el total de ventas entre fechas.

Pues todo esto es bastante sencillo sabiendo donde está cada cosa.

Métodos de pago usados en pedidos

A saber, los pedidos en Magento 1 guardan parte de sus datos principales en la tabla sales_flat_order. La información del método de pago utilizado en cada pedido se guarda en la tabla sales_flat_order_payment.

Entonces, sacando la columna method de la tabla sales_flat_order_payment tendremos todos los métodos de pago utilizados en los pedidos. Esto en lenguaje SQL queda así:

SELECT distinct(sfop.method)
FROM sales_flat_order_payment sfop
ORDER BY sfop.method ASC;

Ventas de los pedidos enviados

El siguiente paso será saber cómo sacar las cantidades de los pedidos completados. A saber, los pedidos completados se quedan con un status=complete y state=complete. Sí, es raro, Magento tiene internamente dos estados en cada pedido. Uno llamado status y otro llamado state. Estos estados están respectivamente en sus columnas. Así la consulta a la base de datos en SQL para sacar las ventas de un método de pago queda así:

SELECT sum(sfo.grand_total) total_sold
FROM sales_flat_order sfo
JOIN sales_flat_order_payment sfop ON sfop.parent_id = sfo.entity_id
WHERE sfo.state = 'complete' AND sfo.state = 'complete' AND sfop.method LIKE '".$methodCode."'
AND sfo.created_at > '".$startDate->format('Y-m-d H:i:s')."'
AND sfo.created_at < '".$endDate->format('Y-m-d H:i:s')."'"

Fíjate que la tabla sales_flat_order_payment se relaciona con sales_flat_order de la forma que marco en negrita.

El código completo

Ahora bien, haciendo un poco de ejercicio con un par de bucles, programando el script completo, nos queda algo tal que así:

<php

require_once __DIR__.'/app/Mage.php';

Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);

// Loading arguments.
if ($argc == 3) {
    $yearFrom = $argv[1];
    $yearTo = $argv[2];
} else {
    echo 'ERROR: Incorrect number of arguments.'.PHP_EOL;
    exit;
}

$sqlConnection = Mage::getSingleton('core/resource')->getConnection('core_read');

// Loading available method codes.
$query = 'SELECT distinct(sfop.method)
    FROM sales_flat_order_payment sfop
    ORDER BY sfop.method ASC;';
$methods = $sqlConnection->fetchAll($query);
$methodCodes = array();
foreach ($methods as $method) {
    $methodCodes[] = $method['method'];
}
//var_dump($methodCodes);

// Printing table in CSV format from January to December..
echo 'PAYMENT_METHOD';
for ($year = $yearFrom; $year <= $yearTo; ++$year) {
    for ($month = 1; $month <= 12; ++$month) {
        echo ','.$year.$month;
    }
}
echo PHP_EOL;
foreach ($methodCodes as $methodCode) {
    echo $methodCode;
    for ($year = $yearFrom; $year <= $yearTo; ++$year) {
        for ($month = 1; $month <= 11; ++$month) { 
            $startDate = new DateTime($year.'-'.$month.'-1 00:00:00'); 
            //echo 'Date from: '.$startDate->format('Y-m-d H:i:s').PHP_EOL;
            $endDate = new DateTime($year.'-'.($month + 1).'-1 00:00:00');
            //echo 'Date to: '.$endDate->format('Y-m-d H:i:s').PHP_EOL;

            $query = "SELECT sum(sfo.grand_total) total_sold
            FROM sales_flat_order sfo
            JOIN sales_flat_order_payment sfop ON sfop.parent_id = sfo.entity_id
            WHERE sfo.state = 'complete' AND sfo.state = 'complete' AND sfop.method LIKE '".$methodCode."'
            AND sfo.created_at > '".$startDate->format('Y-m-d H:i:s')."'
            AND sfo.created_at < '".$endDate->format('Y-m-d H:i:s')."'";
            $amount = $sqlConnection->fetchOne($query);
            echo ','.$amount;
        }
        $startDate = new DateTime($year.'-12-1 00:00:00');
        //echo 'Date from: '.$startDate->format('Y-m-d H:i:s').PHP_EOL;
        $endDate = new DateTime($year.'-12-31 23:59:59');
        //echo 'Date to: '.$endDate->format('Y-m-d H:i:s').PHP_EOL;

        $query = "SELECT sum(sfo.grand_total) total_sold
        FROM sales_flat_order sfo
        JOIN sales_flat_order_payment sfop ON sfop.parent_id = sfo.entity_id
        WHERE sfo.state = 'complete' AND sfo.state = 'complete' AND sfop.method LIKE '".$methodCode."'
        AND sfo.created_at > '".$startDate->format('Y-m-d H:i:s')."'
        AND sfo.created_at < '".$endDate->format('Y-m-d H:i:s')."'";
        $amount = $sqlConnection->fetchOne($query);
        echo ','.$amount;
    }
    echo PHP_EOL;
}

Este script tendremos que ponerlo en el directorio raiz del proyecto Magento para que enganche al Magento. Fíjate que necesita de dos parámetros de entrada que son el año de inicio y el año de fin. Además he marcado un objeto que proporciona Magento para estas cosas, la conexión a la base de datos que se guarda en $sqlConnection.

Ejecutando

Guardamos el script anterior en un fichero, por ejemplo llamado script.php. Si queremos las ventas desde el año 2017 al 2017, lo ejecutamos así desde línea de comandos:

$ php script.php 2017 2017

Veremos la salida por pantalla. Ahora bien, si redireccionamos la salida del script a un fichero, luego podremos visualizarlo mejor. Para esto lo ejecutamos así:

$ php script.php 2017 2017 > ventas_por_metodo_de_pago.csv

Ahora abrimos el fichero CSV con nuestro programa de hojas de cálculo favorito para ver los datos. Si no tienes, es muy recomendable el LibreOffice que lo puedes descargar gratis haciendo click aquí.

Así jugando un poco con los datos resultantes, podemos tener una gráfica de evolución de los métodos de pago. Como la imagen de cabecera de arriba. Podemos ver que algo está pasando en abril y por Navidades del año 2017 con uno de los métodos de pago.

Espero que sirva.

Un saludo.


PHP: qué es un Singleton y para qué nos puede servir

2018-01-21 - Categorías: General / PHP
Coding

Este es uno de los patrónes de diseño de software más sencillos de implementar. Se trata del Singleton, que simplemente es un tipo de objeto de programación. En Programación Orientada a Objetos (POO), tenemos este tipo de objetos que se usan para sólo instanciar uno y exclusivamente uno en todo el programa.

No sabemos cuántas veces ni en cuántos lugares se va a usar el objeto. Pero sí que sabemos que necesitamos que sólo exista uno como máximo. De aquí que viene su nombre de Singleton.

Se dice que viola los principios SOLID del diseño de software porque en sí mismo controla el proceso de creación, pero de hecho, en patrones más avanzados como en las fábricas es necesario implementarlos.

Continuar leyendo..

Magento 1: cómo cambiar la numeración de pedidos, envíos, facturas y abonos

2018-01-02 - Categorías: Magento
Magento logo

Hoy traigo otro pequeño HOWTO para trabajar con las numeraciones internas de Magento 1.9. Los pedidos, envíos, facturas y abonos se pueden numerar. Se pueden poner prefijos por defecto, o a nivel de store, puedes seleccionar distintos prefijos y numeraciones.

En un Magento original sólo te permite hasta una numeración por store para facturas, abonos, envíos y pedidos. Esto incluso se puede mejorar haciendo varias numeraciones por store, pero ya no es una configuración normal. Para hacer 2 numeraciones de facturas en una misma store habrá que controlar el evento de creación de facturas, para así ir reenumerando ‘al vuelo’, mediante programación.

La tabla con las numeraciones

Se trata de la tabla eav_entity_store, por ejemplo:

Magento Eav Entity Store

Las numeraciones siguen todas la misma estrategia. En la columna increment_last_id tendremos el último elemento numerado. Por ejemplo, miremos el último pedido numerado es el P00127586, cuyo prefijo que lo tenemos en la columna increment_prefix es el P. Entonces nos queda que el número 00127586 es la numeración. Para calcular el siguiente le suma 1 y le vuelve a poner el prefijo.

Por ejemplo, si hacemos lo mismo con la última fila, tenemos A18X010000 es el último incremento usado. Su prefijo es el A18X, con lo que para calcular el siguiente le sumará 1 a 010000. Con lo que el siguiente será A18X010001.

Configurando por store

Inicialmente no tenemos definidos valores y se numerarán sólo con números sin prefijos de letras. Pero para definirlos tenemos que tener en cuenta todas las columnas de la imagen:

  • entity_store_id: identificador interno de fila, sólo se usa para numerar cada fila.
  • entity_type_id: identificador de tipo de entidad. Estos tipos de entidades los tenemos en la tabla eav_entity_type, por ejemplo para la entidad de typo pedido tenemos un identificador 5.
  • store_id: identificador de store, podemos verlos todos en el backend de Magento, en ‘Sistema > Gestionar tiendas’. Si pasamos el ratón por encima del enlace de vista de tienda vermos los identificadores en la URL de enlace. Por ejemplo:
    ../admin/system_store/editStore/store_id/7/key/6196861892c917efe3c053d806cb7b1e/
    Al entrar a editar esta vista de tienda (store) tenemos que su ID es 7. Este es el valor que podremos usar.
  • increment_prefix: prefijo de este contador.
  • increment_last_id: último valor usado.

Espero que sirva.

Un saludo.

© 2022 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.