Category: WordPress

  • WordPress Memes

    This is the collection of all the WordPress memes I’ve created.

    You Should Make Plugins

    The West Coast Choppers meme is one of the greatest of all time. I made this for a presentation at the WordPress Lancaster meetup called How to Make a WordPress Plugin to help people get over the two biggest mental roadblocks to making their first plugin: “I don’t know PHP,” and “I don’t want to break my site.”

    Is This a PHP File?

    I made this for the same How to Make a WordPress Plugin presentation to help people understand that PHP start and end tags are what makes parts of a PHP file special, and there might be HTML or something else inside otherwise. This is slight meme abuse because the typical use of this image portrays our hero as naive.

    But Her Editors

    I made this to mock a sentence in Matt Mullenweg’s “New 5.0 Target Date” blog post that turned out to be false.

    WordPress.com is Not WordPress

    Matt Mullenweg used the wordpress.org blog to criticize one of his competitors. Which is the real post?
  • How to convert a Network Active plugin to active on each site

    WordPress multisite allows plugins and themes to be Network Active. This prevents them from being deactivated on any site in the network.

    Instructions

    To change a plugin from Network Active on every site to individually active on each site:

    1. Manually Network Deactivate the plugin from the network plugins page. The wp network command does not yet support plugin deactivation.

    2. Connect to the server via SSH and run this WP CLI command. In this example, superlist-block/superlist-block.php is the plugin basename.

      wp site list --field=url | xargs -n1 -I % wp --url=% plugin activate superlist-block/superlist-block.php

    What does the command do?

    wp site list --field=url produces a list of URLs for all of the sites on the network.

    The | operator passes the results from wp site list to xargs. xargs can take the output from one command and send it to another command as parameters.

    The -n1 flag to xargs processes each line of output from the site list command one at a time. -n2 would send two site URLs to the next command.

    wp --url=% plugin activate hello.php is the command to which xargs is passing the site URLs. It activates the Hello Dolly plugin on each site specified by URL. The % sign is our replacement character where each URL will populate.

    A for loop might be easier to read

    If this code is easier to quickly read and comprehend, read Sal Ferrarello’s post, Loop Through WordPress Multisite Blogs with WP CLI.

    for URL in $(wp site list --field=url); do 
      wp plugin activate superlist-block/superlist-block.php --url=$URL;
    done
  • WordPress 6.7 supports HEIC & I’m the author of an HEIC conversion plugin

    WordPress 6.7 is going to support HEIC image conversion. I’m the author of an HEIC image conversion plugin.

    WordPress core will soon convert HEIC, or iOS format, photos during uploads.

    My free plugin, HEIC Support, just crossed 3,000 active installs.

    Does my copy-or-replace switch remain a useful feature? Do I get obsoleted out of the .org plugin repo on November 12th? Does it shrivel and die quietly? I have no idea. I can’t wait to find out.

    I’m not even an iOS user. I’ve never had any .heic images to convert! I wrote the plugin because @austinginder told me WordPress doesn’t accept iPhone photos at a meetup.

    When it launched, I wrote, “Someday, we will celebrate the death of this plugin.” Will we?

  • How to Submit Your First Plugin Translation

    Submit a second language translation to each plugin you list on wordpress.org.

    Why?

    Once a plugin has a translation, the “Languages:” section appears with a link “Translate into your language.” This link helps strangers using your plugin submit more translations. Plugins with one language do not have this link on their .org directory pages.

    If you speak only English like me, you can likely produce an English (UK) translation of your plugin to provide a second language for your plugin.

    I also hired a translator

    I met someone on twitter who charges four cents per word for English to Spanish translation. WordPress recognizes 14 Spanish language variants. My translator said they would prepare a LATAM (Latin American) Spanish that would also work for other variants. I submitted the translation I bought for all variants, but one or two versions have been approved after 6 months. Many uploaded translation files sit behind tens of thousands of other contributions in queues waiting for review. The WordPress project is in dire need of translation volunteers.

    How

    1. Export the “all current” file from https://translate.wordpress.org/projects/wp-plugins/embed-pdf-gravityforms/stable/en-gb/default/. Replace the plugin slug “embed-pdf-gravityforms” in this URL with your own.
    2. Open the file in VS Code, and populate all translated strings with the untranslated strings using regex find replace
      Find: msgid “([^”]+)”\nmsgstr “”
      Replace: msgid “$1″\nmsgstr “$1”
    3. Make these replacements manually on the “msgstr” lines:
      1. Color > Colour
      2. Zip, Zip Code > Postcode
      3. Customizer > Customiser
      4. Check > Cheque (if referring to a paper form of payment, not “check this out”)
    4. Make sure the .po file is valid with msgcat {filename}
    5. Import the file on the same page where you got the Export using the Import link.
    6. Wait for someone to approve the translations you’ve submitted (<24 hours, 6 months, or longer depending on the language)

    Get word count from .po file

    I found this tool helpful when pricing projects with a translator.

    https://pofile.net/free-po-editor

    How to duplicate a .po file for all Spanish variants

    I wrote this PHP script to copy an es_MX/Spanish (Mexico) .po file into the 13 other Spanish variants.

    How to test a .po file before publishing on translate.wordpress.org

    1. Put the .po file in the /languages folder of your plugin unless a different path is specified in the plugin’s load_plugin_textdomain() call.
    2. Use the load_textdomain_mofile filter to load your files https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#plugins-on-wordpress-org
    3. Call wp_set_script_translations() for each of your script files on the admin_enqueue_scripts hook priority 1000
    4. Run the wp-cli command wp i18n make-json languages/
    5. Navigate to Settings > General > Site Language and select from the list of available languages the one you’d like to test.

    Where to get feedback about translation compatibility and imports

    In the core WordPress slack, there are two channels filled with automated messages about plugin translations. Search for your plugin slug in the Making WordPress slack and look for results in these channels:

    • #polyglots-warnings logs translation warnings generated on translate.wordpress.org when .po files are imported.
    • #meta-language-packs logs language pack notices as plugins are updated.

    These channels will tell you if your plugin is not yet compatible with language packs and if anomalies were found while comparing translations.

    Sample messages

    I found both of these messages to be helpful when working on translations.

    • Plugin is not compatible with language packs: Requires WordPress 4.0; wrong text domain in header
    • Warning: Lengths of source and translation differ too much.

    Why aren’t submitted translations showing up in the plugin directory?

    Volunteers must review all translated strings. 90% of a plugin’s strings must be translated before that language pack becomes available in the Plugin Repository. Visit this page to monitor progress: https://translate.wordpress.org/projects/wp-plugins/embed-pdf-gravityforms/stable/

  • Free Multi-page Form Builder

    This morning, I had to swap out a form builder plugin after some security breach occurred at wordpress.org or at one of its contributors. Page breaks are typically a pro feature in form plugins, with a few exceptions.

    I found Forminator, a form builder plugin that allows multi-page forms to be built for free.

    I don’t always want to activate my Gravity Forms license

    I have licenses to a couple form builders that provide multi-page functionality. Almost every project is Gravity Forms and ACF, though. It’s nice to learn about other systems.

    Forminator provides slider controls to specify numbers or a range of numbers, for example. This feature is unavailable with any paid level of Gravity Forms.

  • PLUGIN_CHECK_PHP_BIN constant

    The WordPress Plugin Review team released Plugin Check. They use it to scan all plugin submissions to wordpress.org, and encourage all authors to run it before uploading plugins to the directory.

    PLUGIN_CHECK_PHP_BIN Error Message

    One error asks us to create a constant:

    Cannot find the PHP Binary file, please define it using the `PLUGIN_CHECK_PHP_BIN` constant

    It’s asking for the path to PHP so it can scan plugin code. Here is some PHP code to create the constant with a sample value of “path/to/php”.

    if ( ! defined( 'PLUGIN_CHECK_PHP_BIN' ) ) {
    	define( 'PLUGIN_CHECK_PHP_BIN', 'path/to/php' );
    }

    How to Find the Path

    In your local environment or while logged into a remote server, use the whereis php command to reveal the path to the PHP binary. In this example from my computer, the path is not the only text output by the command:

    $ whereis php
    php: /opt/homebrew/opt/[email protected]/bin/php /opt/homebrew/share/man/man1/php.1
    

    Where to Run the Command

    If you’re on macOS, open Terminal, type or paste the whereis php command, and press Enter.

    Define the Constant in wp-config.php

    The path is very likely to end in bin/php. Here’s what I added to wp-config.php files running on my computer:

    if ( ! defined( 'PLUGIN_CHECK_PHP_BIN' ) ) {
    	define( 'PLUGIN_CHECK_PHP_BIN', '/opt/homebrew/opt/[email protected]/bin/php' );
    }

    Run the Plugin Check again. If you successfully created the constant with the correct path, your Plugin Check report may be a bit longer.

  • WordPress 6.3 Errors in Dashboard

    Sites running WordPress 6.3 and plugins that add the post__not_in query variable on the parse_query hook are breaking the lists of posts and pages in the Dashboard. I found this bug because I built and maintain a plugin that did exactly this.

    Example pages with errors after updating to WordPress 6.3

    • wp-admin/edit.php
    • wp-admin/edit.php?post_type=page
    • wp-admin/edit.php?post_type={custom_post_type}

    Why?

    The post__not_in query variable makes it easy for developers to hide posts and pages from dashboard users. The parse_query hook is one of the last hooks before the main query runs, and a handful of hide-page-tutorials recommend using it to exclude specific posts or pages from the query.

    How to Fix

    Follow this advice: Avoid post__not_in

    How to find out if a plugin is causing the errors

    Disable all plugins one at a time and check the broken pages. This is the way to troubleshoot almost every problem on a WordPress site. If you cannot deactivate all plugins for this plugin conflict test, create a duplicate copy of the site to use during the test.

  • Escaping and Translating Text in WordPress

    WordPress core provides developers with a handful of functions to escape string content and enable translations into other languages.

    Translatable Strings

    Access the translated version of text used in plugins or themes with one of the following functions.

    <?php
    echo __( 'Hello', 'text-domain' );
    _e( 'Hello', 'text-domain' );
    echo _x( 'Hello', 'The welcome message', 'text-domain' );

    The first, __(), returns the text.

    The “e” in _e() stands for echo. This function outputs the text.

    The “x” in _x() stands for context and accepts an argument to explain to translators the context of the text.

    Escape for HTML

    Escapes strings so they are not parsed as HTML. Characters like < are converted to &lt;.

    <?php
    echo esc_html( 'Hello' );
    echo esc_html__( 'Hello', 'text-domain' );
    esc_html_e( 'Hello', 'text-domain' );
    echo esc_html_x( 'Hello', 'The welcome message', 'text-domain' );

    Example

    <p><?php esc_html_e( 'Hi there! >:)', 'my-plugin' ); ?></p>

    The above code will render the following HTML:

    <p>Hi there! &gt;:)</p>

    Escape for HTML attributes

    Escape strings used in HTML attributes like class="" so they do not break the HTML.

    <?php
    echo esc_attr( 'Value' );
    echo esc_attr__( 'Value', 'text-domain' );
    esc_attr_e( 'Value', 'text-domain' ); // Outputs the value.
    echo esc_attr_x( 'Value', 'The control value', 'text-domain' );

    Example

    <p><input type="text" value="<?php esc_attr_e( 'Something seems "off"', 'my-plugin' ); ?>" /></p>

    The above code will render the following HTML:

    <p><input type="text" value="Something seems &quot;off&quot;" /></p>

    Escape a URL

    Escape strings inside href="" or src="" attributes.

    <?php
    echo esc_url( 'https://coreysalzano.com/' );

    Escape for textareas

    Prevents the text content of a <textarea> from closing the element early.

    <?php
    echo esc_textarea( 'WordPress core provides developers with a handful of functions to escape string content and enable translations into other languages.' );
  • Akismet Alternatives

    Blocking spam on WordPress websites is heavy work. Akismet comes pre-installed on every site, but it’s not free for businesses or the only way to stop spam.

    1. OOPSpam https://www.oopspam.com/
    2. CleanTalk https://cleantalk.org/
    3. Stop Forum Spam https://www.stopforumspam.com/
    4. Project Honey Pot https://www.projecthoneypot.org/
    5. Zero Spam https://www.zerospam.org

    There are a ton of anti-spam WordPress plugins. The criteria for this list of alternatives is “offers an API.”

    Thanks to Austin and Kerch for inspiration and help creating this list.

  • TypeError: c is not a function

    If you’re building blocks for WordPress, and the error TypeError: c is not a function is logged in your browser’s developer console, you may have created a TextControl component and failed to include both value and onChange attributes.

    This code will cause the error:

    <TextControl
    	label={ __( 'Cash/trade', 'invp-payment-calculator' ) }
    	id={ 'trade' }
    	onChange={ changeTradeValue }
    </TextControl>

    This is a correct syntax:

    	label={ __( 'Cash/trade', 'invp-payment-calculator' ) }
    	id={ 'trade' }
    	value={ attributes.trade }
    	onChange={ changeTradeValue }
    </TextControl>
  • Gravity Forms confirmation not working. Instead, “Thanks for contacting us! We will get in touch with you shortly.”

    If you see the default “Thanks for contacting us! We will get in touch with you shortly.” even if you’ve created a custom confirmation for your Gravity Form, your entry may have been marked as spam.

    All entries deemed spam are shown the default message. Login and try again–logged in users will not get caught in the spam filter.

    Also, visit the Entries list in the dashboard, and look for the Spam folder. Your entry may have been saved with the other Spam.

    Where in the Gravity Forms source code does this happen?

    plugins/gravityforms/form_display.php line 3894

  • TypeError: b.map is not a function

    If you’re building blocks for WordPress, and the error TypeError: b.map is not a function is logged in your browser’s developer console, you may have created a SelectControl component and failed to wrap the options in brackets to create an array.

    This code will cause the error:

    			<SelectControl
    				label="Minimum Rating"
    				value={ attributes.minimumRating }
    				options={ 
    					{ label: 'Great Deal', value: 'GREAT_PRICE' },
    					{ label: 'Good Deal', value: 'GOOD_PRICE' },
    					{ label: 'Fair Deal', value: 'FAIR_PRICE' }
    				 }
    				onChange={ onChangeMinimumRating }
    			/>

    This is the correct syntax:

    			<SelectControl
    				label="Minimum Rating"
    				value={ attributes.minimumRating }
    				options={ [
    					{ label: 'Great Deal', value: 'GREAT_PRICE' },
    					{ label: 'Good Deal', value: 'GOOD_PRICE' },
    					{ label: 'Fair Deal', value: 'FAIR_PRICE' }
    				] }
    				onChange={ onChangeMinimumRating }
    			/>