I’m building something on Laravel, using the built-in Auth with 5.7’s new E-mail Verification feature. Today’s task was to tweak the design of E-mail notifications a bit, so I wanted to preview them in a browser. I thought it would be easy, but the built-in E-mails use the Notification system, bypassing Mailable objects. Some Googling didn’t help much, but combining the Notifications documentation and this forum post got let me preview the messages. Hopefully this post can help someone else preview their messages much more quickly than I did today.
The concept is similar to the “Previewing Mailables” documentation: define a route to view your preview on, and return something that will render the message. The trick is that the MailMessage objects that the Notifications’ toMail() methods return don’t have a render() method like Mailable class does, so we need to find something else. Since the built-in Auth uses Markdown mail we can use the Markdown class. A Notifiable object, (the User object for the user who will get the notification), and a Notification are also needed.
For this to work you need to have the right Notifications published to your resources/views/vendor directory. This is done with the Artisan command php artisan vendor:publish --tag=laravel-notifications.
And set up the preview routes:
Route::get('/reset-notification', function () {
// Get a user for demo purposes
$user = App\User::find(1);
// Make a Reset Notification object, (subclass of `Notification`)
$resetNotification = new \Illuminate\Auth\Notifications\ResetPassword( 'some-random-string-this-will-be-much-longer-in-real-life' );
// get the `MailMessage` object
$mailMessage = $resetNotification->toMail($user);
// get the `Markdown` object
$markdown = new Illuminate\Mail\Markdown(view(), config('mail.markdown'));
// Render the vendor.notifications.email view with data from the `MailMessage` object
return $markdown->render('vendor.notifications.email', $mailMessage->toArray());
});
Route::get( '/verify-notification', function () {
// Get a user for demo purposes
$user = App\User::find(1);
// Make a Verify Notification object, (subclass of `Notification`)
$verifyNotification = new Illuminate\Auth\Notifications\VerifyEmail();
// get the `MailMessage` object
$mailMessage = $verifyNotification->toMail($user);
// get the `Markdown` object
$markdown = new Illuminate\Mail\Markdown(view(), config('mail.markdown'));
// Render the vendor.notifications.email view with data from the `MailMessage` object
return $markdown->render('vendor.notifications.email', $mailMessage->toArray());
});
Once the Notification is published and the routes created it is possible to preview the Password Reset and E-mail Verification E-mails in the browser.
I have a git repo that’s become a monster. It’s got at least two WordPress themes, a handful of custom WordPress plugins, some .htaccess files, an artwork directory, and more. Back in the dark times, when it was a Subversion repository this sort of made sense, in that I didn’t have to set up a new repo on the server for each component in the project. But I haven’t touched SVN in a long time, and making new repositories in Git is easy. We’re doing some major work on this client’s sites this fall, so it’s time to break up the giant repository into several smaller ones.
The monolith repository is being split into seventeen smaller components. One of these components is an “artwork” directory, the ~600MB history of which will bloat the git history of all sixteen other repositories if we don’t purge it properly. The goals of this script are:
Avoid typing the same set of commands seventeen times.
Keep the appropriate git history for each of the seventeen components.
Purge any unrelated git history so each component only has its own history in its .git directory.
Github has a help page on splitting a subfolder into its own repository, and another on removing files from a repository’s history, (it’s meant for sensitive data, but works for all data). In theory we only really need the first link, but in testing the entire history of the monolith remained when only following the instructions in the first link, but with the purge and garbage collection commands from the second link the git history is down to an appropriate size.
The script is relatively simple, but relies a bit on directory structure. It should be in the same directory as the monolith repo, (the script doesn’t go into the monolith, it goes in the same containing folder). There are two places to change the script:
Put the directories for extraction into the repos array, (starting on line 5). These paths are relative to the root of the repository.
Put the path of a copy of the monolith repo for use as a source in the master variable, (line 16).
Once that’s done, running ./export-directory-repos.sh should export the repositories.
Here’s the gist:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
A request came in this week from a client with a WooCommerce store:
I just placed an order through our website and had an issue with the state. The customer lives in Oregon, so I typed “O” hoping Oregon would auto populate, but it didn’t. I was stuck having to scroll through all 50 states to find Oregon.
I tried it, and it’s true. Typing “o” doesn’t narrow the list down much, and the way it does narrow it down doesn’t make much sense in this context: The autosuggest on the State field matches the search phrase anywhere in one of the options, so typing “o” gets you a list of all states with the letter “o” in the state name, ordered alphabetically by state name. Go ahead and try it out in a WooCommerce demo shop.
Out of the box WooCommerce uses SelectWoo, which is essentially Select2 with more accessibility. Select2’s default behaviour of matching the typed text to any part of an option makes sense in many places, especially when searching a store or for categories, but for selecting states, (and countries, although slightly less), it’s confusing.
Nothing’s filterable, but we can set a default
Select2 lets us set the callback function used for matching, so we can customize the matching behaviour, but there’s a problem, the settings for SelectWoo are set in WooCommerce’s country-select.js file, and there’s no way to filter them, so we can’t change the matching behaviour on initialization. It turns out that once a Select2/SelectWoo instance is initialized there’s no way to change the settings either.
When I got to here I thought we were out of luck – either I’d be creating a child theme and re-implementing the checkout, or we were going to live with SelectWoo’s default filtering behaviour. Luckily, I found one way to change the matching callback: It’s possible to set the default SelectWoo options callback before any instances are initialized. So that’s what we’re going to do.
Server-Side
First we need to add a bit of Javascript to our checkout page, so we enqueue a small script that’s dependent on the selectWoo script.
And that’s all the PHP we need! Because our solution changes the default matching behaviour we are careful to only enqueue this script on the Checkout page using WooCommerce’s is_checkout() function.
Client-Side
The enqueued Javascript file is pretty simple as well:
(function($) {
// Based on custom matcher here: https://select2.org/searching#customizing-how-results-are-matched
function start_matcher(params, data ) {
// If there are no search terms, return all of the data
if ($.trim(params.term) === '') {
return data;
}
// Do not display the item if there is no 'text' property
if (typeof data.text === 'undefined') {
return null;
}
// `params.term` should be the term that is used for searching
// `data.text` is the text that is displayed for the data object
// Make sure to compare in the same case!
if ( data.text.toLowerCase().indexOf(params.term.toLowerCase() ) === 0 ) {
return data;
}
// Return `null` if the term should not be displayed
return null;
}
$.fn.select2.defaults.set('matcher', start_matcher );
})(jQuery);
There’s a closure so we don’t pollute the global namespace, then we define a new matcher function that matches the beginning of the phrase, and set it as the default matcher. This code runs as soon as it’s ready – it doesn’t wait for a DOMReady event, because by the time DOMReady fires the SelectWoo boxes have probably been initialized already.
Conclusion
I wish we didn’t have to set the defaults for the whole page, but that’s what we’re stuck with at the moment. We could apply different matching patterns to different pages by adding a few more conditions to the PHP block, and a bit more Javascript. This works to make the checkout page work as expected, though.
All the code is up on Github as a WordPress plugin. If it’s useful let me know!
This week a client asked to add estimated ship times to the single-product pages in WooCommerce. Ship times are sometimes different for different products, so adding it as a custom field to each product made sense. As a bonus, there’s a “Shipping” tab in the Product Data metabox that the field can go in. Some Googling led me to Tom McFarlin‘s “Adding Custom Fields to Simple Products with WooCommerce” tutorial on tuts+, which is pretty thorough, but out of date – the actions he’s are no longer in WooCommerce, so it’s time for a new version of that tutorial, my-style. There are three main tasks to accomplish: Add the field to the WordPress back-end, save whatever is in the field, and output the data on the Single Product page. We’ll look at each task separately. The completed plugin is up on GitHub if you want to follow along.
Add the Custom Field to WooCommerce in the WordPress Back-End
WooCommerce provides an incredible number of hooks and filters. We’re going to us woocommerce_product_options_shipping hook, because it runs in the shipping block, but is an equivalent hook for each Product Data tab. Look at the files in the woocommerce/includes/admin/meta-boxes/views directory to understand how the Product Data, (and all the WooCommerce Metaboxes), are created, and what hooks are available.
Now we know what hook we need, it’s time for some code:
function print_admin_field() {
/**
* @var \WC_Product
* @see https://docs.woocommerce.com/wc-apidocs/source-class-WC_Meta_Box_Product_Data.html#42
*/
global $product_object;
if( $product_object->get_type() == 'simple' ) {
$field = [
'id' => 'sif_ships_in',
'label' => __('Ships In', 'jb-sif'),
'placeholder' => __('example: 1-2 days', 'jb-sif'),
'value' => get_post_meta( $product_object->get_id(), '_sif_ships_in', true ), // Pre-fill any values that have already been saved.
'description' => __( 'Add a "Ships in X" message below the Product Meta on the single-product page.', 'jb-sif'),
'desc_tip' => true
];
\woocommerce_wp_text_input( $field );
}
}
add_action( 'woocommerce_product_options_shipping', 'JB\SIF\print_admin_field');
We make sure we have the $product_object variable available, (it’s set in the WC_Meta_Box_Product_Data class, linked with the @see directive), then we make sure we’re adding the field to a “Simple” product type. Then, instead of writing a bunch of HTML, we set some info in an array, and pass it to the woocommerce_wp_text_input() function, (with a backslash, because the plugin is namespaced). WooCommerce has several of these helper fields available that create a form fields and their associated markup so they fit nicely with the rest of the WooCommerce admin.
Notice we’re already getting a value with get_post_meta(). That’s because the same code gets used every time the field is displayed – so if there’s a value in the database we want to display it. This is all we need to properly display a field in the Shipping section of the Product Data. We could add a name attribute to the $field variable, but it defaults to the value of id, so I left it out. Setting desc_tip to true puts the description in the tooltip that appears when someone hovers the circle with the question mark beside the field.
The last line of the code block above hooks our function into the woocommerce_product_options_shipping action. Because the plugin has its own namespace we need to include the full name of the function in the add_action() call.
Save Whatever is Entered in the Field
Again, a game of hooks figuring out how to save the data. Tom’s tutorial from last year used a woocommerce_process_product_meta hook which no longer exists. It looks like it’s been replaced by a woocommerce_process_product_meta_* hook for each product type. Since we’re only using a Simple product, we’ll use the woocommerce_process_product_meta_simple hook. Now we know which hook to use, some code:
In the save() function, (which we can name “save” without collisions because we’re using namespaces), we check the field is part of the $_POST array, check the nonce set by WooCommerce, and save the contents of the field, without forgetting to sanitize the data first.
WooCommerce may already checks the nonce for us, but I am not 100% sure, so I added an extra check.
Output the Data on the Single Product Page
WooCommerce’s plethora of hooks make placing the output relatively simple. I chose to put it right below the “Add to Cart” button on the Single Product page, but if you look through WooCommerce’s templates/content-single-product.php file and the files in the templates/single-product directory you’ll find lots hooks to use – and of course you’re not restricted to the single-product page. If you want to add your output to each product in a list of products take a look in the templates/content-product.php file.
function print_output() {
global $product;
$ships_in = get_post_meta( $product->get_id(), '_sif_ships_in', true );
if( !empty( $ships_in )): ?>
Our print_output() function is pretty simple: Grab the post_meta, check it’s not empty, and output it mixed in with a “Ships In” string. It’s hooked into the woocommerce_single_product_summary action with a priority of 45, which means it appear after the Add to Cart button and after the Product Meta. WooCommerce’s template files are really good at explaining what functions are hooked to actions, and the priority they’re hooked at, which makes it easy to place things on a page without having to copy templates over to your theme.
Conclusion
Adding fields to the WooCommerce part of a WordPress admin area is much easier than adding a whole metabox. You can grab the completed plugin on Github. You’ll see there’s a bit more to the plugin: requirements are checked using Mark Jaquith’s method before embarking on a namespaced plugin with shorthand array syntax.
If this is useful let me know, and feel free to open tickets and send pull requests on Github.
Flush Opcache with Varnish is here to rescue you from the constant annoyance of flushing yet another cache!
If, like me, you use PHP’s Opcache to speed up a site, and you have a Varnish cache, and maybe some other server-side caches, you probably want to flush the opcache, Varnish cache, and any other server-side caches at the same time after changing the PHP files on your server, (like, when you update WordPress, plugins, or themes). I don’t want to click a button for each cache type, or worse, have to log in to the command-line for a sudo service restart varnish after doing updates, especially if I’m doing repeated updates. Flush Opcache with Varnish hooks into Mika Epstein’s excellent Varnish HTTP Cache plugin and flushes the PHP Opcache and the WP Super Cache cache, (if you use WP Super Cache), every time you manually flush the Varnish cache, turning the Varnish HTTP Cache “Clear Cache” buttons into a three-for-one deal.
If you also use the plugin WP Opcache to manage your opcache then WP Opcache will be called to do the actual Opcache flushing so you can take advantage of its automatic rebuilding of the opcache.
I’m already using Flush Opcache with Varnish in production and it’s saving me time every time I update a theme or plugin. You can get it in the WordPress.org plugin directory.