Desarrollamos Software para ecommerce

  • Woocommerce: No pedir Dirección para Productos Virtuales

    Hoy me pidieron agregar un producto variable (PDF descargable) a una tienda.
    Haciendo pruebas vi que cuando se paga, se pide la dirección de facturación y opcionalmente la de envío.
    Esos datos no son necesarios, ya que no habrá envío, y produce fricción.
    Entonces decidí eliminar esos campos del checkout cuando solo se están comprando productos virtuales.

    Lo primero es marcar el producto como Virtual y opcionalmente Descargable

     

    El hook que se utilizará para este fin es

    woocommerce_checkout_fields

    Y para saber si solo tenemos productos virtuales usaremos la función:

    $woocommerce->cart->needs_shipping()
    

    Esta función nos regresa si se tiene un producto que necesita envío, es decir no son virtules.

    Entonces si nos regresa false, procedemos a eliminar los campos:

            unset($fields['billing']['billing_company']);
            unset($fields['billing']['billing_address_1']);
            unset($fields['billing']['billing_address_2']);
            unset($fields['billing']['billing_city']);
            unset($fields['billing']['billing_postcode']);
            unset($fields['billing']['billing_country']);
            unset($fields['billing']['billing_state']);
    

     

    Aquí está el código completo:

    <?php
    /*
     * Plugin Name:       woo-simplify-downloads
     * Plugin URI:        https://urano.dev/plugins
     * Description:       Handle the basics with this plugin.
     * Version:           0.1.dev
     * Requires at least: 6.0
     * Requires PHP:      8.1
     * Author:            Urano Dev
     * Author URI:        https://urano.dev/software-para-ecommerce/
     * License:           GPL v2 or later
     * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
     * Update URI:        https://urano.dev/plugins
     * Text Domain:       my-basics-plugin
     * Domain Path:       /languages
      */
    
    add_filter('woocommerce_checkout_fields','udev_custom_checkout_fields');
    
    function udev_custom_checkout_fields( $fields ) {
        global $woocommerce;
        $only_virtual = !$woocommerce->cart->needs_shipping(); //function to check if there are any physical product in the cart
        if($only_virtual) {
            unset($fields['billing']['billing_company']);
            unset($fields['billing']['billing_address_1']);
            unset($fields['billing']['billing_address_2']);
            unset($fields['billing']['billing_city']);
            unset($fields['billing']['billing_postcode']);
            unset($fields['billing']['billing_country']);
            unset($fields['billing']['billing_state']);
        }
        return $fields;
    }
    
  • WordPress, a Favor y en Contra

    Desde hace algunas semanas hay una pelea en el mundo WP. En base a lo que he leído y lo que he podido constatar, estos son mis pensamientos.

    A favor y En contra

    Estoy a favor del Open Source

    Estoy en contra de que el Open Source sea usado para atacar a un competidor, o para beneficio puramente personal.


    Estoy a favor de WordPress.

    Estoy en contra de que solo una persona tenga el poder de definir, cambiar, o acomodar lo que está bien o está mal, de acuerdo a las circunstancias y no de acuerdo a principios. Que las reglas apliquen para uno y no para todos. Que haya reglas diferentes para empresas amigas de las reglas para empresas enemigas.


    Estoy a favor de contribuir al entorno WordPress. Es algo natural para las empresas y personas que tienen principios. Las personas / Empresas que no tienen principios, duran poco. Ley Universal.

    Estoy en contra de que sea obligatorio u obligatorio selectivamente el contribuir. Todos debemos estar en un mismo estándar.


    Estoy a favor de un liderazgo en WordPress.

    Estoy en contra de un líder que actúa subjetivamente, por ejemplo: haciendo rabietas porque su «mamita confundió WPE con WP» (lo que sea que signifique eso).


    Estoy a favor de la Fundación WordPress.

    Estoy en contra de que la fundación esté bajo el control de una persona no responsable ni madura. Lo mejor es tener un comité o un consejo. Con contrapesos.


    Estoy en favor de la inversión de Venture Capital (VC). Da igual si invierten 1, millón, 500, o 1000. Todo son bienvenidos. Parte del éxito de WordPress es por este capital.

    Estoy en contra de que el líder recibe millones de VC y llama al VC «el cáncer de WP» si un competidor recibe VC, incluso si recibe menos capital.

  • Como usar MariaDB o MySQL con user admin

    MYSQL_PWD=`cat /etc/psa/.psa.shadow` mysql -uadmin psa

  • Themes, Child Themes, WP Hierarchy y otros temas

    Los archivos de plantillas son muy comunes en los temas y se usan mucho en los temas hijos. Por ejemplo, si un usuario quiere modificar el diseño de las páginas de su sitio, puede simplemente copiar `page.php` desde su tema principal a un tema hijo, modificar la estructura HTML, y los cambios se reflejarán en el sitio. Aunque es bien sabido que los archivos de plantillas como `page.php` también pueden ser creados para plugins, siento que la mayoría de los desarrolladores tienden a evitar construirlos debido a la complejidad de crear un cargador de plantillas en un plugin. Primero, me gustaría explicarte la lógica de cómo funciona un cargador de archivos de plantillas; segundo, voy a mostrarte un ejemplo real; y tercero, te voy a mostrar lo fácil que es ahora construir un cargador de plantillas en tu plugin.

    El concepto de un cargador de plantillas es bastante simple:

    1. Determinar el nombre del archivo que se va a cargar.
    2. Determinar las ubicaciones donde buscar el archivo y el orden en que se debe buscar en cada una.
    3. Buscar el archivo en cada ubicación.
    4. Cargar el archivo y dejar de buscar tan pronto como se encuentre.
    5. Recurrir a una plantilla predeterminada si el archivo solicitado nunca se encuentra.

    Al crear un cargador de plantillas en un plugin, el desarrollador usualmente crea una carpeta principal de plantillas (el nombre puede variar) que existe dentro del plugin y que contendrá todos los archivos de plantillas disponibles. Estos archivos luego pueden ser copiados a una carpeta con un nombre específico en el tema activo para ser modificados. Si un archivo de la carpeta de plantillas del plugin existe dentro del tema, se cargará en lugar de la versión predeterminada del plugin.

    Los cargadores de archivos de plantillas como este se utilizan en muchos plugins a gran escala para proporcionar mayor flexibilidad y mejor control a los usuarios avanzados que desean adaptar la salida del plugin a sus necesidades específicas. Algunos ejemplos de plugins que usan cargadores de archivos de plantillas son:

    • Digital Downloads
    • WooCommerce
    • bbPress
    • buddyPress

    Let’s look at a quick example of what a template file loader looks like in a plugin. We will use Restrict Content Pro (RCP) for this example.

    RCP has several template files for the registration form, the login form, and the user’s profile editor form. The login form template, as an example, looks like this:

    <!--?php global $rcp_login_form_args; ?-->
    <!--?php if( ! is_user_logged_in() ) : ?-->
    
    &nbsp;
    
    <form id="rcp_login_form" class="rcp_form" action="&lt;?php echo esc_url( rcp_get_current_url() ); ?&gt;&lt;p&gt;" method="POST">
    <fieldset class="rcp_login_data"><label for="rcp_user_Login"><!--?php _e( 'Username', 'rcp' ); ?--></label>
    <input id="rcp_user_login" class="required" name="rcp_user_login" type="text" />
    
    <label for="rcp_user_pass"><!--?php _e( 'Password', 'rcp' ); ?--></label>
    <input id="rcp_user_pass" class="required" name="rcp_user_pass" type="password" />
    
    <label for="rcp_user_remember"><!--?php _e( 'Remember', 'rcp' ); ?--></label>
    <input id="rcp_user_remember" name="rcp_user_remember" type="checkbox" value="1" />
    <p class="rcp_lost_password"></p>
    <input name="rcp_action" type="hidden" value="login" />
    <input name="rcp_redirect" type="hidden" value="&quot;&lt;?php" />"/&gt;
    <input name="rcp_login_nonce" type="hidden" value="&quot;&lt;?php" />"/&gt;
    <input id="rcp_login_submit" type="submit" value="Login" /></fieldset>
    </form>&nbsp;
    <div class="rcp_logged_in"></div>
    <!--?php endif; ?-->

    This file is mostly straight HTML with a little bit of PHP. A pretty standard template file.

    Restrict Content Pro loads this file through a short code, and when that happens, it looks like this:

    rcp_get_template_part( ‘login’ );
    The rcp_get_template_part() takes care of searching each of the possible locations for a file called login.php and then loading the file if it exists.

    The rcp_get_template_part() function looks like this:

    /**
    * Retrieves a template part
    *
    * @since v1.5
    *
    * Taken from bbPress
    *
    * @param string $slug
    * @param string $name Optional. Default null
    *
    * @uses rcp_locate_template()
    * @uses load_template()
    * @uses get_template_part()
    */
    function rcp_get_template_part( $slug, $name = null, $load = true ) {
    // Execute code for this part
    do_action( ‘get_template_part_’ . $slug, $slug, $name );

    // Setup possible parts
    $templates = array();
    if ( isset( $name ) )
    $templates[] = $slug . ‘-‘ . $name . ‘.php’;
    $templates[] = $slug . ‘.php’;

    // Allow template parts to be filtered
    $templates = apply_filters( ‘rcp_get_template_part’, $templates, $slug, $name );

    // Return the part that is found
    return rcp_locate_template( $templates, $load, false );
    }
    This function does four things:

    Fires a do_action() call so that other plugins can hook into the load process of specific template files.
    Determines the names of the template files to look for.
    Passes the template file names through a filter so other plugins can force it to look for additional or different template files.
    Fires rcp_locate_template(), which performs the actual file lookups.
    Before we have a final picture of how this works, we need to look at rcp_locate_template():

    /**
    * Retrieve the name of the highest priority template file that exists.
    *
    * Searches in the STYLESHEETPATH before TEMPLATEPATH so that themes which
    * inherit from a parent theme can just overload one file. If the template is
    * not found in either of those, it looks in the theme-compat folder last.
    *
    * Taken from bbPress
    *
    * @since v1.5
    *
    * @param string|array $template_names Template file(s) to search for, in order.
    * @param bool $load If true the template file will be loaded if it is found.
    * @param bool $require_once Whether to require_once or require. Default true.
    * Has no effect if $load is false.
    * @return string The template filename if one is located.
    */
    function rcp_locate_template( $template_names, $load = false, $require_once = true ) {
    // No file found yet
    $located = false;

    // Try to find a template file
    foreach ( (array) $template_names as $template_name ) {

    // Continue if template is empty
    if ( empty( $template_name ) )
    continue;

    // Trim off any slashes from the template name
    $template_name = ltrim( $template_name, ‘/’ );

    // Check child theme first
    if ( file_exists( trailingslashit( get_stylesheet_directory() ) . ‘rcp/’ . $template_name ) ) {
    $located = trailingslashit( get_stylesheet_directory() ) . ‘rcp/’ . $template_name;
    break;

    // Check parent theme next
    } elseif ( file_exists( trailingslashit( get_template_directory() ) . ‘rcp/’ . $template_name ) ) {
    $located = trailingslashit( get_template_directory() ) . ‘rcp/’ . $template_name;
    break;

    // Check theme compatibility last
    } elseif ( file_exists( trailingslashit( rcp_get_templates_dir() ) . $template_name ) ) {
    $located = trailingslashit( rcp_get_templates_dir() ) . $template_name;
    break;
    }
    }

    if ( ( true == $load ) && ! empty( $located ) )
    load_template( $located, $require_once );

    return $located;
    }
    This function takes an array of template file names, such as array( ‘login.php’, ‘login-form.php’ ), loops through the list and searches in each of the possible locations for the files. As soon as it finds a matching file, it does one of two things:

    If the third parameter, $load, is true, the file is loaded with the core WordPress function load_template()
    If $load is false, it returns the absolute path to the file
    For Restrict Content Pro, the locations rcp_locate_template() looks are (in this order):

    wp-content/themes/CHILD_THEME/rcp/{filename}
    wp-content/themes/PARENT_THEME/rcp/{filename}
    wp-content/plugins/restrict-content-pro/templates/{filename}
    That’s the entire process. While it is not a ton of code, it is something that can be intimidating to write from scratch (note, I took most of my code straight from bbPress, thanks JJJ!). If you are not writing a large plugin, it can be difficult to justify spending a lot of time on a system like this, which leads me into the next part of this tutorial.

    Gary Jones recently released a Template Loader class that does most of the heavy lifting for you. The class is based on the template loader built into Easy Digital Downloads (which was based off of the one in bbPress), so it works nearly identically to the methods described above for Restrict Content Pro, except it takes an OOP approach.

    I’m not going to walk you through the class, but I am going to show you a quick example of how to use it so that you can easily build template file loaders into your own plugins.

    First, you will copy the main Gamajo_Template_Loader class into a new file in your plugin.

    Second, you will write a new class that extends Gamajo_Template_Loader, like this:

    * Template loader for PW Sample Plugin.
    *
    * Only need to specify class properties here.
    *
    */
    class PW_Template_Loader extends Gamajo_Template_Loader {

    /**
    * Prefix for filter names.
    *
    * @since 1.0.0
    * @type string
    */
    protected $filter_prefix = ‘pw’;

    /**
    * Directory name where custom templates for this plugin should be found in the theme.
    *
    * @since 1.0.0
    * @type string
    */
    protected $theme_template_directory = ‘pw-templates’;

    /**
    * Reference to the root directory path of this plugin.
    *
    * @since 1.0.0
    * @type string
    */
    protected $plugin_directory = PW_SAMPLE_PLUGIN_DIR;

    }
    This class simply defines the prefix for filters, the name of the directory that files will be searched for in from the current theme, and also the plugin’s own directory.

    Third, you include both of these class files into your plugin and instantiate the sub class:

    When put in our short code, it looks like this:

    function pw_sample_shortcode() {

    $templates = new PW_Template_Loader;

    ob_start();
    $templates->get_template_part( ‘content’, ‘header’ );
    $templates->get_template_part( ‘content’, ‘middle’ );
    $templates->get_template_part( ‘content’, ‘footer’ );
    return ob_get_clean();

    }
    add_shortcode( ‘pw_sample’, ‘pw_sample_shortcode’ );
    Note that an output buffer is used because short codes, in order to work correctly, must always return their content, and the template loader includes the files directly.

    To help demonstrate exactly how to use this class, I’ve written a sample plugin that can be viewed and downloaded from Github.

    Having template files available for advanced users of your plugin can dramatically improve the flexibility of your plugin. I would absolutely recommend implementing them in any and all decently large plugins that include output on the front end of the site.

  • Configuraciones para Plesk

    Orden de prioridad para Apache

    Agregue las siguientes líneas a /usr/local/psa/admin/conf/panel.ini usando cualquier editor de texto:

    [webserver]
    directoryIndex = "index.php index.html index.cgi index.pl index.xhtml index.htm index.shtml"
    

    Reconfigure todos los dominios existentes:

    plesk bin domain -l | while read i; do plesk repair web $i -y; done
    
  • Listar todas las llaves de los elementos de un JSON convertido en array

    Últimamente estoy trabajando con formato JSON.

    A veces con mucha información. Por ejemplo para PageSpeed Insights, el json contiene 7,102 datos.

    Para utilizar la información, la paso a un array, y entonces trabajo con los datos.

    Pero cuando la información es extensa, se complica armar la llave que necesitas para acceder cierto dato. Por ejemplo para obtener  el score de las audits que se hacen, el path completo del array sería:

    r['lighthouseResult']['categories']['performance']['score']

    (más…)

  • 20 años de WordPress

    Traduzco la entrada de enero 24 de hace 20 años (2003) en el Blog de Matt Mullenweg. Así nace WordPress

    The Blogging Software Dilemma – Matt Mullenweg

    (más…)

  • The Joys of the Craft Frederick P. Brooks

    NOTA: Traducción de Capítulo 1 Frederick P. Brooks, The Mythical Man-Month, Chapter 1, Addison-Wesley, 1975.

    Frederick P. Brooks murió este 17 de noviembreThe Mythical Man-Month fue uno de primeros libros que leí de Ingeniería de Software. Mucho de estos conceptos siguen vigentes hoy, 45 años después.

    Esta traducción es un homenaje a Brooks.

    Fred Brooks.jpg Mythical man-month (book cover).jpg (más…)

  • Programando con Github Copilot y PHP

    Les comparto mi experiencia con github Copilot. Hace tiempo tuve la idea de tener en el explorador (actualmente uso Edge), cuando abre una nueva pestaña, una imagen y una cita o quote, seleccionadas o curadas previamente por mí.

    En twitter sigo a @naval, y varias de sus frase, me parece muy lógicas, aunque poco comunes.

    Por otro lado, tengo varios proyectos actualmente, y estoy por iniciar otro muy demandante la siguiente semana. Y necesitaba hacer algo pequeño, que me diera esa sensación de logro, de terminar algo.

    Y finalmente, dando una vuelta por mis repositorios de github (privados todos hasta hoy) vi una invitación a usar Copilot.

    Entonces decidí probar Copilot para este pequeño proyecto.

    (más…)

  • Debug con Woocommerce

    Como desarrolladores a veces necesitamos alguna información, el valor de una variable, los parámetros que llegan a un hook, o simplemente saber si el flujo pasó por cierta parte del código. Por eso hoy te enseño a hacer Debug con Woocommerce.

    La documentación de Woocommerce lo explica. Pero yo te ayudo a entenderlo.

    ALERTA: El contenido está dirigido a programadores. El código mal implementado puede afectar a tu Tienda.

    Empecemos por una función:

    wc_get_logger()

    Primero veamos el significado de Log. Yo lo traduzco como bitácora o registro. Es un lugar donde vas guardando información de eventos. Normalmente incluyendo el tiempo en que sucedieron.

    Woocommerce ya viene con un visor de logs. Para acceder al visor la ruta desde wp-admin es: WordPress Dashboard > WooCommerce > Status > Logs

    Imagen que muestra un log usado para Debug en Woocommerce

    Como puedes ver hay varios logs. Uno de ellos es el mío. Esto es posible con lo que te explico en este post.

    En cualquier lugar de tu código donde necesites guardar informaicón, estos son los pasos:

    Paso 1.- Llamar la función wc_get_logger():

    $logger = wc_get_logger();
    $context = array( 'source' => 'udev-log' );
    

    Paso 2.- Llamar la función que escribe en el log:

    $logger->debug( 'Información detallada para Debug', $context );
    $logger->info( 'Eventos relevantes', $context );
    $logger->notice( 'Eventos normales', $context );
    $logger->warning( 'Eventos excepcionales, pero no son errores', $context );
    $logger->error( 'Errorre que neceitan atención', $context );
    $logger->critical( 'Condiciones críticas', $context );
    $logger->alert( 'Acción inmediata necesaria', $context );
    $logger->emergency( 'Houston, llamando Houston', $context );
    

    La diferencia entre ellas es la clasificación de la información. El primer parámetros en un string con el contenido a guardar en el log. El segundo es opcional, si quieres tener tu propio log. El segundo es un array, que puede incluir información que quieras pasar al logger. Esto es útil en dos situaciones: cuando quieres un log exclusivo para ti, y cuando creas tu propio logger y necesitas información adicional. Pero eso es otro tema.

    Tu propio log.

    El parámetro source lleva el nombre del log. Bueno como ves en la imagen el nombre del log incluye el nombre que le pasas. En mi caso le paso como valor ‘udev-log’ y generó un log: udev-log-2022-09-04-4b3c742ca243b2f0dab1393322ec142f.log, por lo que se ve que incluye la fecha y un identificador único.

    Según la documentación esos logs se borran después de 30 días.

     

    En mi caso yo prefiero llamar directamente la función wc_get_logger():

    wc_get_logger()->debug('Info de debug');
    

    Y cuando quiero crear mi propio log:

    wc_get_logger()->debug('Info de debug', array('source' = 'udev-log'));

    Si te gustó este post, mira los temas de Temas de Curso Desarrollo en Woocommerce – Urano Dev/ y suscríbete a mi lista de espera.