Blog

  • Mudbugs Hockey Jersey Patches

    Mudbugs Hockey Jersey Patches

    New shoulder patches for my hockey team’s 2018 rebrand

  • Convert alphabetical Google Sheet column names to numbers

    Lately, I’ve been working with large Google spreadsheets that have many columns. When you write QUERY statements to pull data from one sheet into another, you have to use Col1, Col2, Col3 column names instead of A, B, and C. It’s hard enough to remember the letter O is the 15th letter of the alphabet. There is no chance that I am going to memorize that column BH is the 60th. I made this calculator to solve this conversion:

    [alphabet_number_calculator]

    One of my favorite tricks when a team member needs some data is to create a new Sheet, query data from the primary sheet, and share that subset of the data while protecting the A1 cell so the formula doesn’t get mangled. This method lets our friend get only the columns she needs and live updates in the form of new rows just like the huge primary sheet. It works by passing an IMPORTRANGE call into the first parameter of QUERY.

  • Migrating a GravityView Without Losing Fields

    Exporting a GravityView and importing it into a different site is easy, and there are official instructions right here.

    This process works very smoothly when the Gravity Forms and GravityViews on both sites are identical. That’s usually not the world I live in, however. I’m a back-end developer in sites that sometimes have multiple teams working on them. I am usually only responsible for some of the Gravity Forms in the site, and that means the IDs of the forms I import into the site aren’t predictable.

    Migrating a GravityView from one site to another is easy as long as the ID of the Gravity Form on which the GravityView is built does not change. If the ID of the underlying form changes, the view loses all the fields on the multi, single, and edit lists. This is frustrating because GravityViews take quite a while to configure, and those field lists are the bulk of the work.

    Let’s Run Some Database Queries

    If you have the ability to run MySQL queries against the database of your WordPress installation, you can run a couple queries after importing a GravityView to update the view with the ID of the Gravity Form on the new site.

    Query #1

    UPDATE wp_postmeta SET meta_value = 1 WHERE meta_value = 42 AND '_gravityview_form_id' = meta_key;

    You need to change the numbers 1 and 42, which are the new Gravity Form ID and the old Gravity Form ID, respectively. The IDs of your Gravity Forms are listed when you click Forms in the dashboard. Also, take care to make sure your table prefix is the default wp_ like this example, or change wp_postmeta to match.

    Query #2

    UPDATE wp_postmeta SET meta_value = REPLACE( meta_value, 's:7:"form_id";s:2:"42";', 's:7:"form_id";s:1:"1";' ) WHERE '_gravityview_directory_fields' = meta_key;

    Look for our form IDs "42" and "1" again. If your form IDs aren’t two and one digits like this example, you’ll also have to change the s:2: and s:1: that precede the values to match. These are pieces of a serialized PHP array, where s means string and the :2 means that the string "42" has a length of two characters. (Likewise for s:7 identifying "form_id" as a string with a length of seven characters.)

  • Sometimes, an array is passed to the get_callback provided to register_rest_field() instead of an object

    When using register_rest_field() to add fields to terms in the WordPress REST API, the $object sent to your get_callback function will be an array instead of an object like the documentation describes.

    An object will still be passed, sometimes, too. Here is an example of how I work around this problem:

    
    	function add_api_term_fields() {
    
    		//location-phone-hours
    		register_rest_field( 'location', 'location-phone-hours', array(
    			'get_callback'    => array( $this, 'get_term_meta_via_rest' ),
    			'update_callback' => array( $this, 'set_term_meta_via_rest' ),
    			'schema'          => array(
    				'description' => __( 'An array of phone numbers and hours of operation for this location.', 'inventory-portal' ),
    				'type'        => 'string',
    				'context'     => array( 'view', 'edit' ),
    			),
    		) );
    	}
    
    	static function get_term_meta_via_rest( $term, $attr, $request, $object_type ) {
    		$term_id = 0;
    
    		if(  is_array( $term ) ) {
    			$term_id = $term['id']
    		} else {
    			$term_id = $term->term_id;
    		}
    
    		return maybe_serialize( get_term_meta( $term_id, $attr, true ) );
    	}
    

    Why is this happening? See the definition of prepare_item_for_response() method on line 683 of wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php. The call to add_additional_fields_to_object() on line 725 is the method that passes an array to your get_callback.

    Perhaps this is an acceptable design. I believe the only reference to the data type of $object in the documentation is this comment. It certainly feels like a bug.

  • How to add an additional webhook to an Elementor Form

    Elementor Pro includes a form builder widget, complete with a webhook to which all form data can be sent and a redirect URL to send users after successful submissions. I have been tasked with adding a second webhook to a form because the first was being used for an integration with Zapier.

    We need to use the elementor_pro/forms/new_record hook to make this work, and it is possible to prevent the form from submitting if our additional webhook fails. Here is a simple plugin that adds a webhook to all Elementor Forms sitewide:

    https://gist.github.com/csalzano/dfd754e0fe8b6ac10731fad8f257c0bf

    If you need to target a specific form, access the form name or ID using the $record object.

    //Get the Elementor form ID
    $form_id = $record->get_form_settings( 'id' );
    
    //Get the Elementor form name
    $form_name = $record->get_form_settings( 'form_name' );

  • Lititz Homebrewer’s Cooperative

    Lititz Homebrewer’s Cooperative

    A logo for the local homebrew club. Join our mailing list at brew.lititz.beer.

  • WordPress REST API returns 500 error when updating serialized meta with an unchanged value

    Take care when updating posts via the WordPress REST API to not send unchanged serialized meta values. The entire update will fail and return a 500 error. The JSON response looks like this:

    {
    	"code": "rest_meta_database_error",
    	"message": "Could not update meta value in database.",
    	"data": {
    		"key": "meta_key_name",
    		"status": 500
    	}
    }

    This error comes from line 300 of wp-includes/rest-api/fields/class-wp-rest-meta-fields.php, and to understand why calls to update_metadata() return false for unchanged and serialized meta values, read on.

    Let us look at like 196 of wp-includes/meta.php. We learn that if a previous value is not passed to update_metadata(), that function looks it up for a comparison and returns false if they match. During REST API calls, the previous value is not provided.

    So why doesn’t the REST API return an error for updates to any post meta values?

    The REST API tries to short-circuit and avoid calling update_metadata() on line 293 of class-wp-rest-meta-fields.php when the incoming value has not changed, but the conditions were not designed for serialized data:

    		if ( 1 === count( $old_value ) ) {
    			if ( $old_value[0] === $meta_value ) {
    				return true;
    			}
    		}

    By this time, the meta value in $old_value has been run through maybe_unserialize(), but the new value in $meta_value has not. This short circuit fails, update_metadata() is called & returns false instead of true, and the API responds with a 500 error as a result.

  • Setting post_parent with the WordPress REST API

    WordPress 4.9.4 does not support the post_parent attribute in the REST API, so I wrote a plugin that does.

    Download here: https://github.com/csalzano/wp-api-add-post-parent

    Someday, WordPress core will allow the manipulation of the post_parent attribute, and I look forward to deleting this plugin and this blog post when they become obsolete. Until then, I believe this implementation resembles how core will handle this.

     

  • Mitel 5000 Phone Administration Notes

    This is the Mitel 5340 phone for which these instructions were written
    The company where I work migrated to a new phone provider, and I am publishing the notes I’ve taken over the last few years before I delete them. Most of these instructions begin inside the Mitel Administration & Diagnostics software.

    How to reset a voicemail box password when someone forgets their password

    1. Go to Voice Processor > Devices > Mailboxes, and choose the mailbox to which you need access.
    2. In the right pane, find Passcode.
    3. Right click the hashes and choose Edit Passcode.

    How to check another mailbox’ voicemail

    1. Dial 2501
    2. Press **
    3. Enter the mailbox number
    4. Enter the mailbox password
    5. Press #

    How to change the email address where voicemails are sent as attachments

    1. Go to Voice Processor > Devices > Mailboxes.
    2. Expand the extension of interest and click Email Synchronization.
    3. Change the Email Address for Voice Messages setting.
    Note the Email Client Message Format setting that allows MP3 or WAV.

    How to access the admin voicemail box

    1. Dial 2500 to reach the voicemail system
    2. Press *
    3. Enter the mailbox number (1999 in our case) and password (also 1999 in our case)

    How to add an extension/phone to a hunt group

    1. Go to System > Devices and Feature Codes > Hunt Groups.
    2. Expand the group extension, and double click Members.
    3. Right click in the right hand pane and choose Add to List…

    How to add a recording to the main announcement played for all callers

    1. Go to Voice Processor > Devices > Applications.
    2. Choose the application that corresponds with our main day and night announcements. (in our case these were 2505 for day and 2506 for night)
    3. Click on Day Greetings.
    4. Right click in white space in the right pane and choose Add to Day Greetings List…
    5. Follow the prompt to add a recording to the list.
    6. Drag the recording to set the order in which the recordings will play.

    How to change the length of time a caller is left on hold in a hunt group before an announcement is made or a transfer is made to another extension

    1. Go to System > Devices and Feature Codes > Hunt Groups.
    2. Expand the group extension, and click Timers.
    3. The Announcement setting is the length from beginning of call to when the announcement is delivered.
    4. The Recall setting is the length from beginning of call to when a call to an extension is made.

    How to change the name of the human using an extension

    Go to System > Devices and feature codes > Phones

    How to forward all your calls to an external number

    1. Press the special key, which is the X inside a circle to the right of the blue call history button.
    2. Enter the feature code 355
    3. Enter the number to which calls should be forwarded, including any external line prefix (8 in our case)

    How to hear or change a greeting

    1. Dial 2500 to reach the voicemail application
    2. Press *
    3. Enter the admin mailbox number (1999 in our case)
    4. Enter the password then # (also 1999 in our case)
    5. Choose hidden option 9, then 3, then 1 to change (or 2 to listen)
    6. Enter the recording number given to you (example 023) then #
    7. Record your message and listen to the instructions carefully to save your recording
  • The Lancaster Beacon

    The Lancaster Beacon

    I made this icon for a twitter account I started a while ago, @LancasterBeacon. News, culture & events in and around Lancaster, Pennsylvania.

  • Spinners and loading animations built into WooCommerce

    WooCommerce is a big plugin that ships with several JavaScript loading animations. There is no reason to roll your own when you need one.

    This week, I am hacking on WooCommerce to convert “Add to Cart” buttons to prevent redirecting users to different pages. Here’s a great tutorial I used to accomplish this goal. Without page loads, users benefit from loading animations in understanding that something is happening that they otherwise cannot see.

    Buttons

    Adding a loading class to buttons dims the button and shows a white spinning gear inside the button to the right of its label.

    Usage

    jQuery('button.single_add_to_cart_button').addClass('loading');
    jQuery('button.single_add_to_cart_button').removeClass('loading');

    Containers

    WooCommerce also provides JavaScript to block and dim a whole content container because it ships with BlockUI. BlockUI is a jQuery plugin that WooCommerce uses to obscure the user interface while executing synchronous JavaScript. You can see how it’s used by WooCommerce core when removing an item from the shopping cart on the cart page.

    Usage

    jQuery.blockUI();
    jQuery('.product').block();
    jQuery('.product').unblock();

    The default message is “Please Wait…” but you can provide your own text in the options array.

    jQuery('.product').block({message:'Updating cart...'});

    There are more examples on the plugin’s demos page.

     

  • Thickbox.js: a modal API hidden in WordPress core

    Yesterday, I needed to create a modal, but I didn’t want to create a modal.

    “Surely, this is already in WordPress.”Corey Salzano

    So, I went looking for a modal in WordPress core, and I was delighted when I found one. Thickbox is JavaScript library that was created more than one decade ago and began shipping with WordPress in 2008.

    Why Thickbox in WordPress is great

    1. It’s a modal API built into core
    2. It’s a simple white box with an optional title, close button, and no offensive styling
    3. You don’t have to build it

    Perhaps Also Not Great

    I quickly noticed one shortcoming of the implementation. There is no way to specify the height and width of the modal using percentages. The values are required to be supplied in pixels, unless you use this JavaScript that enables the use of percentages:

    Thickbox’s fate as a part of WordPress core is questionable. Here’s a six year discussion of whether it should still be included, and WordPress core also bundles jQuery UI Dialog via the ‘jquery-ui-dialog’ script handle.

    Even if WordPress core drops this library in a future version, a plugin could be written to maintain compatibility. Today, I prefer thickbox because its implementation is so simple.