rsync -v -a -e 'ssh -p 2200' user@example.com:www/wp-content/uploads/ uploads/
// Keep in mind that rsync works left to right (from => target).
// -v verbose lets you know things are happening
// -a archive maintains preserves directory structure
// -e executes a piece of code ( I use it to target a specific port for ssh ).
This snippet will allow you to quickly sync a local directory with a remote one. I’ve used this snippet repeatably to sync large media folders to my location installation of a wordpress site.
You’ll need SSH access to the remote server for this method to work.
Also note that 2200 is likely NOT your port and you should configure it your specific server.
Depending on what you use for your local wordpress environment (I use VVV) – you might want to sync all your remote images with your local installation.
But this might not be easy if you’ve got a large website (like a store) with thousands of images and limited storage on the server. You can’t zip them up because even compressed they are too big!
Rsync to the rescue!
rsync is a wonderful little command line tool which will literally sync a remote directory like wp-content/uploads.
Assuming you’ve got SSH access to a remote site – you can simply specify a port and username to get access to the directory.
rsync -v -a -e 'ssh -p 2200' yourname@yourdomain.com:path/to/wp-content/uploads/ relative/path/to/local/wp-content/uploads/
Good job you did it
Please not that this may take awhile to complete depending on how big your directory is. I wrote this quick post while waiting for my mine to complete. You can of course continue to developer even with out the images but damn does that get annoying and slow really quick.
Webpack is awesome – and using it with wordpress is easy! Beyond performance increases – Webpack can greatly improve your development experience.
In this post I will demonstrate how I setup my WordPress theme to use Babel and BrowserSync.
This tutorial assumes you have a development environment setup. This blank storefront child theme is perfect for quick modifications and working through tutorials.
Setup
If you want to follow the tutorial, you’ll want a local wordpress installation and npm. If you want to use blank theme to follow the tutorial, cd into your themes folder and clone the repository above.
$ git clone https://github.com/gerrgg/storefront-child-theme-starter.git
$ cd storefront-child-theme-starter.git
Activate the theme and visit the site – you should see “hello” in the top left corner if the theme installed properly.
Webpack?
If you’ve never used Webpack – its a build tool which compiles your code into static optimized assets. Not only does Webpack greatly improve load times – it enables the use of tools like Babel to compile ES6 to ES5 or BrowserSync for automatic browser reloads on save.
$ npm install webpack
First install Webpack – (use npm init --yes if you dont have package.json file).
$ npm i --save-dev webpack webpack-cli
Next set the webpack scripts in your package.json file – this is what we will run to actually use webpack later.
The output is where the bundled assets will be put and how to name them – these are the files we will be enqueuing into our wordpress theme or plugin.
The output property tells webpack where to emit the bundles it creates and how to name these files. It defaults to ./dist/main.js for the main output file and to the ./dist folder for any other generated file.
https://webpack.js.org/concepts/#output
Next configure where out bundled assets will be put – we have two so we use the [name] syntax which Webpack knows means “use the key from the entry points”.
We also set the path for these compiled files to end up in the ‘assets’ folder.
This results in all our frontend JavaScript being compiled to /assets/js/frontend.js and all our admin code at /assets/js/admin.js.
The finished webpack-config.js
So we built a webpack-config.js file at the root of our project – which lets Webpack know where our JavaScript files are now and where the compiled results should go.
The finished Webpack configuration looks like this:
As mentioned above – using Webpack has a massive net positive affect on your applications performance and your personal development experience. Webpack and wordpress work beautifully together and allow for massive gains in versatility in your wordpress development.
“Hello” from the front
First add a simple console.log() to our source JavaScript files (the before).
// /src/frontend/front-index.js
console.log('hello from the front')
// /src/admin/admin-index.js
console.log('hello from the back')
Enqueue compiled assets
Now we are ready to use wp_enqueue_script to hook up our compiled assests with out wordpress theme or plugin.
Note that frontend and admin scripts are enqueued on different hooks – you’ll need two functions in your functions.php file.
Now time to see all your hard work pay off – if we’ve done everything correctly all we gotta do is run our watch script and see the compiled results.
Go to your command line and type npm run watch
Visit your site and see your console.log().
What did that do?
That was alot of work – and all we did was console.log a silly message – why did I even bother writing this?
Setting up Webpack with our theme has provided us limitless potential.
Cuts down on HTTP Requests
First – by bundling our JavaScript files we no longer suffer from having to make 100 different HTTP requests all our JS files. This DRAMATICALLY cuts down on load times if you’ve chosen the sane route of modularization your code.
Promotes better code
Second – if by chance you are a maniac such as myself at one point (this year) you went for performance over sanity and stuffed thousands of code into a single file which fueled your entire project.
By using webpack – you dont have to do this. You can have a beautifully organized project with lots of different files & functionality automatically compiled into easy-to-digest static files for the browser to munch on.
Your quality of your code greatly improves by modularizing your code into little bits – improving bug resistance, readability and your sanity.
Loaders and plugins
Loaders and plugins multiply the vast number of opportunities provided by webpack * 11.I wont get into the nitty-gritty right now – click those links if your interested.
I’m most interested in installing Babel and BrowserSync
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
Essentially – babel lets us write code in higher level forms for developer enjoyment and automatically converts it into more compatible code all browsers can understand.
Install Babel
First we are going to Install babel and a few of its dependencies.
Next create a babelrc.json file at the root of your project – this is where babel will automatically look for any configurations we’ve added.
{
"presets": ["@babel/preset-env"]
}
Finally add a module to your webpack configuration file to run all Javascript through the babel-loader we installed and setup.
Add this after output in webpack-config.js.
...
},
module: {
rules: [
{
// Look for any .js files.
test: /\.js$/,
// Exclude the node_modules folder.
exclude: /node_modules/,
// Use babel loader to transpile the JS files.
loader: "babel-loader",
},
],
},
...
Test Babel
To test babel all we need to do is run npm run watch and cat assets/js/frontend.js to see the compiled results – but if all we have is a console.log('hello') babel’s not going to have much to do – lets do something ES6(-y).
If this looks funky – all we are doing is using some new ES6 syntax and setting up the default export to be the hello function we created.
Now in your frontend entry point file src/frontend/front-index.js delete the console.log statement and import in our hello module we just created.
Your front-index.js file should look like this now:
// /src/frontend/front-index.js
import hello from "./hello";
hello();
Run npm run watch (if your not already) to compile your code.
The browser friendly compiled results
The output is what you might expect – a simple “hello” logged to the console, but if you look at assets/js/frontend.js you’ll see that babel did quite alot.
And that is just one small example of how awesome BabelJS – It allows us to write ES6 and automatically converts it to a more compatible version of JavaScript.
But you know whats annoying? Saving and CTRL+R everytime we make a modfication – and if you’ve used something like Create React App you know exactly what your missing.
Automatic/Hot Reload with BrowserSync
Hot reload is one of the coolest things I’ve ever done to up my DEV game and its surprisingly easy to setup with Webpack.
We are going to use something called BrowserSync. Essentially all it does is automatically reload the browser EVERYTIME we CTRL+S our files.
At the bottom of the config file (just before the end of the config object) add BrowserSync as a Webpack plugin.
...
},
plugins: [
new BrowserSyncPlugin({
files: "**/*.php",
proxy: "http://your.devserver.here", // your dev server here
}),
],
};
// Export the config object.
module.exports = config;
Above we are adding BrowserSync to Webpack.
Files
We set files: "**/*.php" – telling webpack to watch for changes in our .php files and reload the browser when they do.
Proxy
We set proxy: "http://your.devserver.here"so browser sync knows which server to proxy all the files from.
Essentially this is where your local wordpress environment is setup – mine was at "http://one.wordpress.test" and I am using VVV.
“Hello automatic browser refresh”
Run Webpack npm run watch and a tab should open at http://localhost:3000/ with your wordpress installation running.
Go ahead – write something in the functions.php file to test it out like: “Hello automatic browser refresh”
Webpack is awesome
As you can see Webpack is awesome – this just the surface of awesomeness presented by the tool. As I learn more about webpack I will try to add what I learn and share how to create the ultimate Webpack setup.
So above: we are calling the ‘woocommerce_get_availability_text' action, we are adding our ‘gregbastianelli_change_backorder_message‘ function to the pipeline, setting a priority of 10 and expecting 2 parameters.
Change backorder message globally
If we visit the source code for woocommerce_get_availability_text you can see it returns two parameters in the apply_filters function.
Expected Parameters
$availability is the text message returned when a product shows its on backorder.
$this is the product in question.
Now that we know which data is being passed to us – we can setup our function.
Step-by-step
Setup up the function – write documentation and do stuff.
function gregbastianelli_change_backorder_message($text, $product)
{
/**
* Allows developer to change backorder message site-wide
* @param string $text
* @param WC_Product $product
*/
// do stuff
}
First we want to set a backorder message.
$backorder_message = "Will ship in January 2021";
Next we want to set a condition so our backorder message only shows on products on backorder.
We also include a return under our condition so the work done by the previous functions in the pipeline is passed on if our condition is not met.
The end results (tl;dr)
In a hurry? Here are the end results.
How to change backorder message store wide
<?php
add_filter(
"woocommerce_get_availability_text",
"gregbastianelli_change_backorder_message",
10,
2
);
function gregbastianelli_change_backorder_message($text, $product)
{
/**
* Allows developer to change backorder message site-wide
* @param string $text
* @param WC_Product $product
*/
$backorder_message = "Will ship in January 2021";
if ($product->managing_stock() &&
$product->is_on_backorder(1)) {
return $backorder_message;
}
return $text;
}
How to change the backorder message for a specific product
If you want to have different messages for different products, include a check for a specific id as well.
...
$backorder_message = "Will ship in January 2021";
$target_id = 272; // include ID
if (
$target_id === $product->get_id() && // check for ID
$product->managing_stock() &&
$product->is_on_backorder(1)
) {
return $backorder_message;
}
return $text;
}
You did it!
Look at you. Changing your very own backorder message in woocommerce like a DEV PRO!
GatsbyJS is awesome because it allows developers to quickly design, develop and deploy projects. Gatsby sites are automatically optimized for speed and allows us to focus on building something great.
Why use a Code Syntax Highlighter?
If your personal website is littered with pre and code tags – you might want to consider using a code syntax highlighter.
Syntax highlighters are abundant, easy to use, improve readability and really up your dev game.
With syntax highlighting
Without syntax highlighting
I think the images speak for themselves.
Highlight.js
Highlight.js homepage has a interactive demo for syntax highlighting
As mentioned above – there are plenty of options out there when it comes to code syntax highlighters.
We are going to choose highlight.js because it is well-known, easy to use and comes with lots of theme styles.
Next create a helper file at /src/utils/helper.js.
$ touch src/utils/helper.js
Import the library
// /src/utils/helper.js
import hljs from "highlight.js"
Pick a code theme
Highlight.js has lots of themes available – you can find them all by visiting the official repository, the homepage or running ls in the module’s styles folder.
I chose ‘atom-one-dark’.
// /src/utils/helper.js
import hljs from "highlight.js"
import "highlight.js/styles/atom-one-dark.css"
Configuration
Unless you have some built in meta data or class names which signify the language in your code blocks – you will probably want to use the auto-detect feature.
Trouble is, highlight.js has over 180 languages and detection can be tricky.
I overcame this by narrowing the search to just the languages I know I use in these blocks
Essentially we are looping through the elements, double checking it is an object then replacing the node with a new element created by the highlighter.
Wrapping up
Finally export the helper – the final result should be:
The new helper we wrote is great but how do we use it? See this was the tricky bit for me.
GatsbyJS + WordPress work together by querying your WordPress database for the appropriate data.
A post’s content is passed as a large string of HTML and the only way to use that HTML in react is to use dangerouslySetInnerHTML.
dangerouslySetInnerHTML
dangerouslySetInnerHTML is React’s replacement for using innerHTML in the browser DOM. In general, setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack.
As per the offical react documentation this property name is purposefully scary to remind you its dangerous to set the innerHTML unless your careful.
Luckily our content was written in the WordPress CMS and has already been sanitized for our viewing pleasure.
Highlighting the code blocks
Finally we are going to use the helper we created.
I’m going to assume you have a running Gatsby site with a template setup to display your single posts. If not – the GatsbyJS + WordPress documentation is wonderful and got me started quickly.
Your post will look something like this – where we are creating a content div and passing our posts content via the dangerouslySetInnerHTML property.
// /src/templates/blog-post.js
Import React, { useEffect } from 'react'
export default function BlogPost({ data }) {
const post = data.allWpPost.nodes[0]
return (
<Layout>
<div id="single">
<h1 className="title section-header">{post.title}</h1>
<div
id="content"
dangerouslySetInnerHTML={{
__html: post.content,
}}
/>
</div>
</Layout>
)
}
Most important to understand is we are passing the HTML of our post directly to div#content.
UseEffect
UseEffect is a react lifecycle method – essentially a spot/time in the code where we can run our own functions. Because we are forced to use dangerouslySetInnerHTML – we cannot access or modify the post’s content before hand.
We can however use UseEffect to run our code highlighter after the component is loaded into the DOM and the content created.
Add import your helper and run the highlightCode function in the useEffect block.
My favorite thing about graphQL (besides the simple syntax) is the IDE – this allows your to quickly build queries, explore the schema and experiment.
Using the graphQL IDE to quickly form a query for all posts from my wordpress site.
Getting WPMenu Items
The WPGraphql plugin we installed on our existing wordpress site allows our to query the WP_Menu API – this is what allows our gatsby site to work with Menus created in the WordPress admin section at: Appearance > Menus.
The WordPress admin section for building menus.
Copy the following query into your IDE to ensure you can get your menu items properly:
Running the query should return an object with a list of menu items – including parentID, path, label and children (if any).
The results of your menu item query should look similar to this image
Assuming you’ve got the proper data returned, now we are set to build a Menu component and feed it our queried data.
Creating our Menu in React
Now that we have a query, we are going to want to bring that data into our Gatsby application.
First, create a basic Header component at src/components/header.js.
// src/components/header.js
import React from "react"
const Header = () => {
// query data here
return (
<header className="header">
<h1>Gatsby + WordPress Menu's<h1>
</header>
)
}
export default Header
Since header is not a “template” we must use useStaticQuery to contact the WordPress Menu API. Since we already created the query in the IDE – this will be easy.
First import graphql and useStaticQuery from gatsby
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
const Header = () => {
...
Next setup the query function at the first line in the Header component.
...
const Header = ({ siteTitle }) => {
const data = useStaticQuery(
graphql`
{
// our query will go here
}
`
)
...
Finally copy the query we built in the IDE at http://localhost:8000/___graphql into the query statement – the final result should look like this:
Re-run gatsby develop and console.log(data) to test the query.
Houston, we have a problem.
You might have noticed that there is an issue with our query. Currently we are getting all the menu items in the menu – regardless if they are nested or not.
To fix the issue – we will want to filter out the children by checking if a menu item has a parentID.
Currently our query is returning duplicate menu items – “WordPress” is child of Tutorials as well as its own menu item below.
Filter out the children
We dont want nested menu items passed to the Menu component becuase it will result in duplicates and not reflect how the menu was build in the admin section (the entire point of this post).
Luckily modern Javascript makes it quite easy to filter an array with filter.
Add this line after the query:
...
// filter only parents
const menu = data.wpMenu.menuItems.nodes
? data.wpMenu.menuItems.nodes.filter(menuItem => menuItem.parentId === null)
: null
return(
...
Now we are checking to see if there are any menu items, if yes filter out the top-level menu items – ELSE – return null.
Console.log(menu) and you should only see the top-level menu items.
Console.log(menu) returns a filtered array of top-level menu items.
Finally – we can pass our data to a menu component which will take care of all the list-item logic.
Quickly create a Menu component at src/components/menu.js
// src/components/menu.js
import React from "react"
const Menu = ({menu}) => <p>im gonna be a menu one day</p>
export default Menu
Import your menu and pass it your filtered array of menu items.
import { graphql, useStaticQuery } from "gatsby"
import React from "react"
import Menu from "./menu"
const Header = ({ siteTitle }) => {
...
return (
<header className="header">
<h1>Gatsby + WordPress Menu's<h1>
<Menu menu={menu} />
</header>
)
}
Finally we are ready to create a Menu component which will handle all the logic of displaying menu items.
Building the Menu component
The menu component will be responsible for listing our menu items – it will loop each menu item and check if it has any children.
If there are no children menu items – we will display the current menu item as a standard link.
If there ARE children – we will display it as a dropdown menu item which will show the children on hover.
Setup the menu – loop the items
First we will setup the menu, loop through each item in the menu and check if it has any children.
// src/components/menu.js
import React from "react"
const Menu = ({menu}) => (
<ul className="menu">
// this is where we will do the loop
</ul>
)
export default Menu
Next we will loop through each item in the menu and check if it has children
wc_get_template is great for breaking up a complex plugin into simpler parts.
By default wc_get_template is designed to search in /wp-content/plugins/woocommerce or /wp-content/themes/yourtheme/woocommerce for a template – not in your plugin folder.
Editing wc_get_template with a filter
Luckily we can use a filter to edit the behavior of wc_get_template to search in our plugin if its searching for a specific file path (e.g ‘templates/yourtemplate.php).
** A filter is essentially an array of functions the application will run through before returning a result.
First, add our own function to the filter.
// modify wc_get_template to search in plugin directory
add_filter('wc_get_template', 'gerrg_get_template', 10, 2);
Next we define the function which will modify the behavior of wc_get_template to search in our plugin folder. The function takes 5 parameters but we only need the first two:
$located – the results of searching in the woocommerce folder (default behavior)
$template_name – this is what we will use to set a condition in our gerrg_get_template function,
Lets write some code
The goal of this function will be to get wc_get_template to search in our plugin directory instead of in the default woocommerce folder path.
function gerrg_get_template($located, $template_name)
{
// if we are searching for a specific file
if ('path/to/my/template.php' === $template_name)
{
// return the path to that file
return plugin_dir_path(__FILE__) . $template_name;
}
// otherwise work as normal
return $located;
}
The function is not too complicated – essentially we added a simple conditon to the wc_get_template function.
If wc_get_template is passed a SPECIFIC path (e.g. ‘path/to/my/template.php’) the function will return your plugin’s directory path to the file.
If the condition is not met, revert back to normal function (look in WC folder and theme’s WC folder).
Wrapping up
The filter modifies the functionality of wc_get_template under very specific circumstances.
function gerrg_woocommerce_product_loop_start(){
/**
* opens up any category and shop pages as a boostrap row.
*/
return '<div id="gerrg-archive" class="row">';
}
add_filter('woocommerce_product_loop_start', 'gerrg_woocommerce_product_loop_start', 999);
Add bootstrap .col-* to products
Now we need to add bootstrap colomn classes to woocommerce products by hooking into the woocommerce_post_class filter.
.col-6 – 1/2 the row on mobile
.col-sm-3 = 1/4 the row on small displays
.col-xl-2 = 1/6 the row on large displays
function gerrg_add_bootstrap_cols_to_product( $class ){
/**
* adds bootstrap grid classes to all category and shop pages.
*/
$class[] = 'col-6 col-sm-3 col-xl-2';
return $class;
}
add_filter('woocommerce_post_class', 'gerrg_add_bootstrap_cols_to_product', 30, 3);
Wrapping things up
Finally we close up our shop row and have a functional bootstrap grid system on all woocommerce shop, category & taxonomy pages.
Just like be began, we adding some html to close our div by hooking into the woocommerce_product_loop_end filter.
function msp_woocommerce_product_loop_end(){
/**
* closes up any category and shop pages
*/
return '</div>';
}
add_filter('woocommerce_product_loop_end', 'msp_woocommerce_product_loop_end', 999);
You did it!
Good job, if you have any questions or concerns or have a better way to succeed than above
Open up your function and include a check to prevent this from running on the admin side (it causes an error).
function gerrg_programically_add_to_cart(){
/**
* Looks for hard coded ID's in $targets array, check if ID meets product quantity and adds reward if so
*/
if( is_admin() ) return;
Next create a targets array, include the ID of all items you wish to target.
For variable products you must use the variation ID’s. I am targeting all sizes of a specific shirt.
These are the variations I am targetting. All sizes of a shirt.
This next bit is just a fancy woocommerce method of checking if something is in the cart.
We will use the helper method you added earlier to generate a cart key to pass to woocommerce to check for the item we are rewarding (otherwise they’d get so many!).
// key for quick searching in cart
$reward_item_key = WC()->cart->find_product_in_cart( gerrg_get_product_cart_key( $reward ) );
Now we will loop through the cart and look at EACH item in the cart.
foreach( WC()->cart->get_cart() as $cart_item ){
We make sure to use the ID given, sometimes working between different product types can cause problems.
// get real ID
$id = ( $cart_item['variation_id'] === 0 ) ? $cart_item['product_id'] : $cart_item['variation_id'];
Now we will loop through each ID in our $targets array.
// loop needles
foreach( $targets as $target ){
Now we are going to add the condition. In this specific circumstance, I need 3 things to be true.
The target ID is in the cart
That item has a quantity of 3 or more.
and finally, the reward is not ALREADY in the cart.
// if needle in haystack, quantity over 20 && reward not in cart
if( $target === $id && $cart_item['quantity'] >= 20 && ! $reward_item_key ){
Add the reward to the cart.
// Add reward
WC()->cart->add_to_cart( $reward );
Close everything up.
Discount the free gift
Now we are going to need to make that free gift free.
// this is where fees are calculated
add_action('woocommerce_cart_calculate_fees', 'gerrg_discount_free_gift' ) );
Then add the following function
function discount_free_gift( $cart ){
/**
* Look for free gift in cart, discount item if in cart.
* @param WC_Cart
*/
$product = wc_get_product( $this->reward );
if( ! $product ) return;
$reward_item_key = WC()->cart->find_product_in_cart( $this->get_product_cart_key( $this->reward ) );
foreach( WC()->cart->get_cart() as $cart_item ){
$id = ( $cart_item['variation_id'] === 0 ) ? $cart_item['product_id'] : $cart_item['variation_id'];
foreach( $this->targets as $target ){
if( $target === $id && $cart_item['quantity'] >= 20 && $reward_item_key ){
// Meets all condititions? Add discount (fee).
$cart->add_fee( 'Free Gift', ( $product->get_price() * -1), true );
}
}
}
}
Very similar to the last function we loop the cart and check if the targets are in the cart, the quantity is met AND the reward is in the cart as well.
SSH Keys are wonderful and easy to set up. You can SSH keys to quickly log into a remote server (like your website) from the command line WITHOUT a password prompt.
How does it work?
Essentially SSH works by generating two keys (strings) on your machine: a public and a private one. You place the public key on a external server and when you log in the server makes the connection and access is granted.
How do I do it?
Setting up SSH can be done in as little as two steps:
Generate SSH key Pair
Copy public key to remote server (where you want to login)
Generate SSH Key Pair
First generate the SSH key pair with ssh-keygen.
ssh-keygen -t rsa
You will be asked where you want to store the file and whether or not you want a password, I just click ENTER twice.
Copy the public SSH key
Use ssh-copy-id to quickly move the public SSH key to the remote server.
ssh-copy-id user@example.com
You must know the username of server you are trying to access.