Magento es un gran CMS para montar tiendas online. Está muy desarrollado teniendo en cuenta el SEO, el marketing, para tener muchos dominios con la misma web, muchas configuraciones de impuestos, de precios distintintos por web, distintas plantillas para cada sitio web.. todo desde un mismo panel de control.
Es tan flexible desde el mismo panel de control, que ha llegado a extremos de tal forma de que necesita todo tipo de cacheados, tablas temporales donde guardar indexada la información, así como también Varnish y Redis. Todo esto junto es una combinación explosiva y todo un reto hacerlo funcionar todo. Así que en este post vengo a hacer una review del funcionamiento de estos índices de datos.
En este post de la serie de iniciación a Symfony, traigo una review del perfilador de Symfony, el profiler. Es una gran herramienta de desarrollo web que nos brinda Symfony. Podemos ver muchos datos internos de cómo funciona todo por dentro: la petición y respuesta, rendimiento, envío de formularios, validaciones, datos de las excepciones, logs… pasando por Doctrine, enrutamiento.. Continuar leyendo..
Hoy de nuevo vengo con otro CODE-KATA o HOWTO, esta vez para montar un gestor de archivos web. Recopilando joyas de la informática vengo a traer el FMElfinderBundle. Subir ficheros a una web, renombrarlos, editarlos es una tarea compleja. Si usamos este bundle, que es un vendor de Composer preparado para Symfony Flex, nos podremos olvidar de construir todo esto haciéndonos la vida mucho más fácil.
Es decir, que tenemos un bundle o vendor para Symfony, que nos permite gestionar archivos mediante el navegador. Así podemos subir ficheros de imagen para luego usarlos en la web. También permite otro tipo de ficheros una vez configurado. Incluso te permite editarlos si son de texto dentro del mismo gestor de archivos.
De nuevo traigo otro CODE-KATA, para montar un blog en Symfony Flex, Symfony 4. Así en unos pocos minutos tenemos el esqueleto de lo que puede ser un blog. También valdría para hacer páginas corporativas, incluso si lo que queremos es un editor de fichas de productos también. Es decir, este es un pequeño tutorial para arrancar un nuevo proyecto en Symfony, creando es esqueleto de una aplicación web que va a ser un blog, con un editor de texto avanzado del tipo WYSIWYG.
WYSIWYG quiere decir What You See Is What You Get. Es decir, estos editores de texto son como los que tienen WordPress, Magento, Prestashop.. en donde se editan las páginas visualmente dentro del mismo navegador. Con este tipo de editores puedes organizar los textos, darles tamaño a las letras, alineación, subrayados, colores, etcétera.
He tenido problemas para poner un popup de Mailchimp en esta web. Así que aquí estoy compartiendo la solución. He tratado de usar la utilidad que viene en el panel de control de Mailchimp. Pero todo apunta a que tengo algún conflicto entre los Javascripts de Mailchimp, los Javascripts de mi plantilla, algún plugin de cacheado o del minificado del código fuente. En fin, que no funcionaba y he terminado antes haciendo el popup artesanal que se puede ver en la barra lateral derecha de la web.
Si has tenido el mismo problema, aquí dejo mi solución, que espero que funcione en el 100% de las webs. Simplemente necesitas tener instalado en la web jQuery, el resto viene todo en el código de abajo. Se puede copiar y pegar en el widget de WordPress donde lo quieras y adaptarlo para tu web.
Continuando con el post anterior, traigo otro code-kata, o HOWTO, para hacer ping a una web, para empezar. Digo para empezar, porque esto no es más que el comienzo de una serie de acciones muy potentes sobre servidores web. Así podemos interactuar con un sitio web automática y remotamente. Este es uno de los mecanismos básicos de comunicación entre sistemas informáticos. Es decir, esto se puede reutilizar para que una web se comunique con otra web, con otro sistema informático, leyendo información o enviándola.
Para esto vamos a usar el gran CURL, que va muy bien para trabajar con el protocolo de las webs. Aunque como reza en su web, es compatible con muchos otros protocolos:
DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, Telnet and TFTP. curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, HTTP/2, cookies, user+password authentication (Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos), file transfer resume, proxy tunneling and more..
Esta forma de hacer un simple ping web se puede escalar todo lo que quieras. Es decir, de esta forma es como trabajan internamente los módulos de métodos de pago, envío/lectura de feeds, las APIs de los CMSs, por ejemplo. Las acciones que se ejecutan mediante la comunicación con APIs sobre HTTP/HTTPS. Y claro, cómo no, para las archiconocidas y tan queridas API REST sobre HTTP. Todas estas acciones tienen un origen (cliente) y un destino (servidor).
Vamos a usar en este caso CURL, que es una herramienta del sistema operativo que podemos usar desde PHP. Si estás en GNU/Linux, puedes instalar tanto CURL como el módulo de PHP que lo usa así:
sudo apt-get install curl php7.2-curl
Si estás en Windows, Mac, o cualquier otro sistema operativo tendrás que ir a:
Si ejecutamos esto desde línea de comandos veremos algo tal que así:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://jnjsite.com/">here</a>.</p>
</body></html>
El contenido de la web se está pintando por pantalla: jnjsite.com te invita a redirigirte a https://jnjsite.com que es la versión segura de la web.
Viendo información de la respuesta
Para esto podemos modificar el script anterior y sacamos mucha información de la respuesta:
En este caso nos está pidiendo hacer una redirección 301 a la web con HTTPS. En teoría deberíamos de seguir esta redirección. Así que..
Siguiendo redirecciones
Modificando el script anterior, tenemos haciendo las redirecciones lo siguiente:
<?php
$curlHandler = curl_init('jnjsite.com');
$response = curl_exec($curlHandler);
while(curl_getinfo($curlHandler)['http_code'] >= 300
and curl_getinfo($curlHandler)['http_code'] < 400){
// new URL to be redirected
curl_setopt($curlHandler, CURLOPT_URL, curl_getinfo($curlHandler)['redirect_url']);
$response = curl_exec($curlHandler);
}
var_dump(curl_getinfo($curlHandler));
curl_close($curlHandler);
Ejecutamos, y revisando por pantalla el resultado tendremos que finalmente se paran las redirecciones en https://jnjsite.com/ con un código de respuesta 200. He marcado en negrita lo nuevo con respecto al script anterior para redirigir hasta la página de destino.
Terminando
Para dejarlo limpio el script, sólo quedaría comprobar finalmente si la página ha dado un código OK de respuesta. Marco en negrita lo nuevo:
<?php
$curlHandler = curl_init('jnjsite.com');
curl_setopt($curlHandler, CURLOPT_RETURNTRANSFER, true);
curl_exec($curlHandler);
while (curl_getinfo($curlHandler)['http_code'] >= 300
and curl_getinfo($curlHandler)['http_code'] < 400) {
// new URL to be redirected
curl_setopt($curlHandler, CURLOPT_URL, curl_getinfo($curlHandler)['redirect_url']);
curl_exec($curlHandler);
}
if (curl_getinfo($curlHandler)['http_code'] >= 200
and curl_getinfo($curlHandler)['http_code'] < 300) {
echo 'OK'.PHP_EOL;
} else {
echo 'KO'.PHP_EOL;
}
curl_close($curlHandler);
A partir de aquí sólo queda hacer las acciones que consideres si tienes un OK o un KO.
Una tarea bastante importante a la hora de posicionar una página web es asegurarte de que sigues online. Si has contratado el alojamiento a una empresa no tendrás que preocuparte mucho por el estado del sistema operativo del servidor. Pero hay otros aspectos aparte del servidor que necesitan de tu atención. Puedes pensar que una página web basta con montarla con un buen CMS, que puedes dejarla online y ahí seguirá porque no hay razón para que deje de funcionar. Pues nada más lejos de la realidad, cuantas más cosas tenga tu web, más cosas pueden fallar.
Es decir, si tienes una página artesanal de un único fichero estático es difícil que deje de funcionar. Pero si tienes un CMS, quizá un WordPress, Prestashop, Drupal o Magento.. ya empiezas a tener más elementos que mantener. Los módulos pueden ser inestables, pueden engancharse las arañas de los buscadores, usuarios que llegan a bugs involuntariamente, etcétera.. No digamos ya si tienes muchas visitas que generan contenido dinámicamente.
Las páginas web son como los coches, necesitan un mantenimiento, unas revisiones. Sino, tarde o temprano, dejarán de funcionar. Así que si quieres curarte en salud, puedes tener un sencillo script que compruebe si sigue online una web.
Estos últimos días me han pedido sacar informes de un Magento. Ha sido muy interesante porque no me esperaba tener esta información en el CMS, pero sí, ahí estaba.
Resulta que Magento viene con un atributo de coste de los productos, que es de sistema. Dicho atributo de producto tiene el código cost. Además, en la información que se guarda en cada pedido tenemos el precio original, este coste del sistema en el momento de la venta, y más información. Con esto podremos saber el margen de beneficio que hubo en el momento de la venta.
Es importante notar que supongo que ya usas el atributo de coste del sistema, o que tienes un ERP enganchado a Magento que te lo está manteniendo actualizado con cada pedido de compra.
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?
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:
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:
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:
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í:
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.
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.