Category: WordPress

  • Editing Terms & Term Meta with the WordPress REST API

    Here are some JavaScript snippets to manipulate WP_Term objects in WordPress using the REST API and the Backbone JavaScript client library.

    Insert a term

    Delete a term

    This next example assumes you’ve used wp_localize_script() to make the REST API endpoint and a nonce available in an object myplugin. If you need help doing this, please leave a comment below and I’ll expand this example.

    Edit a term meta value

    Let’s add a term meta value to identify the number of speeds in this automatic transmission.

    Thanks for reading. If you know a better way to edit terms and term meta in JavaScript, please leave a comment below.

  • What is $posted_data passed to Contact Form 7’s wpcf7_posted_data hook

    For a ContactForm7 form that has this source:

    <div class="wpcf7-lead-widget">[text* contact-name maxlength:50 placeholder "Your Name (required)"]
    [email* email maxlength:50 placeholder "Email (required)"]
    [text phone maxlength:15 placeholder "Phone"]
    [vehicle_form_field]
    [textarea comments x3 placeholder "Questions and Comments"]
    [submit class:_button class:_button-small "Check Availability"]
    [hidden context id:context "contact"]
    [hidden do-not-send-mail]</div>

    The $posted_data that is passed via the wpcf7_posted_data hook looks like this:

    Array
    (
    [_wpcf7] => 10610
    [_wpcf7_version] => 5.1.1
    [_wpcf7_locale] => en_US
    [_wpcf7_unit_tag] => wpcf7-f10610-p7983-o1
    [_wpcf7_container_post] => 7983
    [g-recaptcha-response] =>
    [contact-name] => Corey
    [email] => [email protected]
    [phone] => 8005556666
    [inventory-post-id] => 7983
    [comments] => Super interested in this sandbox
    [context] => contact
    [do-not-send-mail] =>
    )

    The first item is the form ID, and all items after g-recaptcha-response are the values of the fields provided by the the user (or the source in the case of the hidden fields). inventory-post-id is the value of a drop down created by the shortcode in our form, [vehicle_form_field].

  • 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' );

  • 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.

     

  • 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.

  • WPEngine caches WordPress REST API GET responses

    Logged out users of a website hosted at WPEngine may receive cached responses to REST API calls via the GET method. API calls work correctly while logged in, but paths in the /wp-json/ directory are cached like much like the rest of the front-end.

    How to prevent cached REST API responses

    As of June 2017, the only way to disable this behavior is to open a ticket. WPEngine support technicians can create tickets based on live chat sessions.

    Excluding a directory from caching happens at the install level, so a new request via ticket must be initiated after each new install is created.

    UPDATE: WPEngine made this change to all 49 installs in my account in less than 24 hours.

  • Presenting at the October Philly #burbswp meetup

    I am presenting at the October Philly ‘burbs meetup @ Michael’s Deli in King of Prussia, PA. More details

  • When renaming your WP admin user, don’t neglect your comments

    A few months ago, popular people in the WordPress community (including Matt) made a push to stop using the user name “admin.” A considerably-sized brute force attack targeted this user name specifically since so many users never change or create an administrator with another name.

    I followed the wisdom of the crowd and this post I found that walks you through the process of creating a new user and deleting “admin” while moving all the posts in the process. It wasn’t until a few weeks later when I noticed a problem.

    I like to use themes that highlight my comments. This allows users to quickly find my updates about plugins and answers to questions that have been asked in a sea of one hundred comments. Well, all the comments I wrote while my user ID was 1 and my user name was admin were no longer highlighted.

    There is a user_id column in the wp_comments table that is not changed when you delete a user and move their posts to a new owner. You need to run a MySQL query like this to implement the fix:

    UPDATE wp_comments SET user_id = 11132 WHERE user_id = 1

    Before executing a query like this on a WP database, the new administrator’s user ID needs to be identified so you can change 11132, which is one of my user IDs. If you do not know how to find a user ID, search this page for “user id.”