Switching from Jekyll back to WordPress

I set up this blog on WordPress in 2008. In 2015 I moved it to Jekyll and back filled those old posts in 2018. Now I’m back on WordPress with the entire archive. Why? I want to post more and WordPress is more conducive to that.

With Jekyll I had to create a new markdown file with all of the yaml front matter I wanted and my automated templates were never quite right, then I had to get the Jekyll site running to preview the post (running the inevitable Jekyll and gem upgrades and troubleshooting issues with some plugin that broke), then run the custom build process I made to commit the changes, push them to Github, build the site, then deploy the changes to S3 and Cloudfront with s3_website, which is no longer being maintained. It was “rickety as hell” as an old family saying goes.

With WordPress, Pressable keeps core up to date for me, so I just open the site, write the post, and click publish. No messing around with build tools, no finicky yaml templates, and no annoying Ruby and Java dependencies to fight with.

Also, with WordPress, basic things like Search and Category Archives are available by default instead of something you need to hack on. The Plugin ecosystem is much better, too.

The Jekyll site build and all the tools I built around it was a fun project, but I’m glad to be back on WordPress. I recently started working at Automattic, which of course played a role in my decision to switch back, but I had planned to make the switch long before I had applied.

The speed of the 100% static Jekyll site served via Cloudfront was slightly faster than this WordPress site, but the difference isn’t big enough to matter to me. The Pressable CDN and object caching is pretty dang fast, and as I said above, I care more about being able to update the site faster and easier than I could with Jekyll.

Getting started

I had big ambitions for this project: I wanted to build a new theme from scratch adhering to the WordPress-Extra ruleset for PHPCS and the starter theme my team uses. I kept putting it off, preferring to be offline when not working. I was itching to get my site migrated, so I decided to modify an existing theme with a child theme and focus my energy on the migration.


I essentially combined three sites into one.

  • Old WordPress posts: I’m a digital hoarder, so I still have the database for my 2008 WordPress site. I loaded it up in a local instance, ran some upgrades so it would work with WordPress 5.5, and then used the typical WordPress WXR files to import them to the new site. I temporarily put my old /wp-content/uploads/ folder on the server hosting cagrimmett.com so that the import would also pull in the featured images.
  • Microblog posts: micro.cagrimmett.com was still live and I keep WordPress up to date there
  • Jekyll posts: I heavily customized my Jekyll RSS feed to include all kinds of extra metadata that it normally wouldn’t. I then used the excellent WP All Import Pro to parse the XML and map it to WordPress fields and download and import the media.
    • Attempting to do this and getting stuck? Shoot me an email and I’ll do what I can to help!

Reading list

I built out a cool reading list feature in Jekyll using yaml data files and a template that grouped the books I read by year and displayed the count. I was able to rebuild it pretty quickly with a custom post type, custom fields, and two queries: One to group the books by year and sort by date read, and the other to display the counts.

Check out the reading page, my favorite page on the site!


I thought I’d have a big issue with redirects, but with some careful planning about 95% of old links just work.

By going back to the old databases from existing WordPress sites, I was able to use existing post IDs, which is great because my old links used the post ID permalink structure. Post ID permalinks redirect to the post by default, even if you have a different permalink structure.

For the Jekyll posts, I was able to match the permalink structure I had on the Jekyll site, category/year/month/day/slug, so those work, too. The only issue I ran into is that I had some uncategorized posts in Jekyll and Jekyll leaves uncategorized out of the permalink while WordPress doesn’t. So I installed the Redirection plugin to add some redirects for those and monitor 404s.

Ongoing issues

I do have a few issues left to clean up:

  1. Code blocks with the Rouge syntax highlighter I used in Jekyll are a bit garbled. They need some styling to make them pretty again.
  2. JavaScript embeds in posts don’t work. I had a bunch of d3.js tutorials with interactive examples that don’t work right now. As a stop-gap I’ve linked to the old Jekyll version that is still accessible on S3, but I need to go through and fix those.

I plan to eventually build out my own theme from scratch and I want to build some of my own custom blocks for a few ideas I have.

But first, back to regular blogging.

Advent of Code, Day 12: Ember Simple Auth

Day 12!

I tried to add user authentication with EmberFire and Ember Simple Auth. I get through the provider workflow with Twitter, see a new user in Firebase, see the correct user data in the network panel in Chrome, and the ember_simple_auth-session cookie has “authenticated” in the content field. But session.isAuthenticated never seems to be true.

I must be doing something wrong, but I don’t have the time to figure it out today. Time to keep packing. I’m buying a house tomorrow and moving in this weekend!

Advent of Code, Day 11: Rendering Charts from Ember Data

Day 11!

After some frustrating trial and error and searching through docs with lots of words I don’t quite understand, Dave Wasmer kindly helped me figure out how to get Ember Objects (the results of Ember data queries) in a format I’m used to working with: Something that looks like a regular array of objects, or “POJO” as Dave said.

The solution ended up being calling the map method on the array-but-not-really-array and toJSON on each item in that. Then I get something back that looks like [{name: ..., startDate:...}, {...}]

model.plants.map(c => c.toJSON()); 

This allowed me to render the charts with Ember Data stored in Firebase.

Tomorrow: Digging back in to D3’s update pattern and getting the chart automatically updating when adding a new plant. Dave’s helpful suggestion was to look into the “data down, action up” pattern.

Thanks, Dave!

Advent of Code, Day 10: Setting up Firebase

Day 10!

I had a conversation on Twitter with Sam Selikoff and Ilya Radchenko, then later on Slack with Ilya and Dave Wasmer about using Firebase + EmberFire + Ember Data vs GraphQL + Hasura (Sam’s suggestion) vs GraphQL + Fauna (Ilya’s suggestion) for my data needs. I appreciate their inputs and arguments, but I ultimately went with Firebase + EmberFire for two reasons:

  1. I don’t want to set up and maintain an API server for this project. That knocks out Hasura.
  2. Fauna looks cool, but I don’t know either Ember Data or GraphQL that well, so I’ll have to learn one. Firebase + EmberFire + Ember Data is a little more battle tested and there is more documentation and search results for it vs Fauna, so I’m going to stick with that. I don’t want to be stuck in the dark on this project or constantly bug Ilya or Dave about stuff I don’t know. I do that enough at work.

So that means I set up Firebase and EmberFire today. It was surprisingly easy! The quickstart guide was solid. I was saving Ember Data to Firebase and retrieving it in my routes in no time.

I modified my Add a Garden form to save to Firebase, then transitioned to the Edit route where I passed along the ID of the garden I just created an retrieved the garden record’s data in the route. Then I read the Ember Data relationships docs and was able to both save and retrieve a garden’s related plants.

Then I got stuck: I tried to pass the plant data to the calendar component to render the chart and I can’t get it to work. The data isn’t coming through as I expect. I assume it has something to do with relationships in Ember data as promises, but haven’t quite figured the solution out yet. So that will be tomorrow’s project.

I feel like I’m making progress, though! It was super satisfying to see my collections and documents appear in Firebase.

Advent of Code, Day 9: More Ember Data

Day 9!

  • My problems with yesterday’s ember data store not being recognized magically disappeared when deleting node_modules and running yarn to install them again. Node, man. So good, but so frustrating sometimes.
  • I got plants and gardens showing up in the ember data store after form submissions, which I verified with the Ember Inspector plugin.
  • I added a form for creating gardens.

garden data plant data

I thought I’d be able to get it all working locally before setting up EmberFire and Firebase, but after seeing that IDs for Ember Data are usually assigned on the server and having a talk with with Dave Wasmer and Ilya Radchenko, it sounds like I might be better off setting up Firebase now instead of trying to get Mirage to work. That is a little more than I have time for today, so that will be tomorrow’s goal.

Advent of Code, Day 8: Learning Ember Data

Day 8! Another short session of work on a plane before I go home and pack up my apartment. Today I finished separating different functional parts of my components into individual components and started integrating Ember data.

I hit an issue I wasn’t able to research since I wrote it only a place without internet access, so the below might be wrong.

My models:

export default DS.Model.extend({   garden: DS.belongsTo('garden'),   startDate: DS.attr('date'),   name: DS.attr('string'),   color: DS.attr('string'),   daysToMaturity: DS.attr('number') });  export default DS.Model.extend({   name: DS.attr('string'),   startDate: DS.attr('date'),   plants: DS.hasMany('plant') }); 

Tomorrow’s goal:

  • Figure out why store is throwing errors for me

Advent of Code, Day 7: Figure out routes

Day 7! Today I’m traveling, but I got some work done on the plane: I figured out my routes and started breaking apart components to their individual functions.

My routes:

Router.map(function() {   this.route('login');   this.route('signup');   this.route('gardens', function() {     this.route('edit', { path: ':slug' });     this.route('new');   });   this.route('public-profile', { path: ':user_slug' }, function() {     this.route('garden', { path: ':garden_slug' });   }); }); 

Tomorrow’s goal:

  • Finish breaking the components apart
  • Get the new routes working

Advent of Code, Day 6: Plant Addition Flow

Day 6! Short update today after tons of phone calls, move packing prep, and packing for a trip.

Today’s progress

  • Removed the mocked plant data for my testing and built out the plant addition flow:
    • If there are no plants, hide the chart, show the form, and show a header
    • After adding a plant, hide the form, render the chart, and show the Add a Plant button
    • Only add the resize listener after the chart renders

I also tried to fix a bug with the color picker where it wouldn’t close, but didn’t solve it.

Amanda gave me another good idea today: For items like tomatoes, it would be good if there was another bar at the end that was lighter to signify a harvesting time window.

Today’s commits:

  1. first use flow

Tomorrow’s goal:

  • Dig in further to tailwind for styling
  • Read the Ember Data docs
  • Use D3’s update() to update the chart instead of destroying and redrawing it
  • Break the components apart

Advent of Code, Day 5: Form Components

Day 5! I didn’t think I’d make this much progress today, but I’m happy I did.

Today’s progress

  • Built reusable form components (button, input, form, label) with the help of EmberMap
  • Updated the chart
  • Integrated a color picker addon ember-pickr
  • Discovered and fixed a bug with the way I was calculating the scale.
  • I didn’t realize how much stuff I thought was just regular Ember in our app is actually addons. A bunch of things I use at Crash every day didn’t work at first in plant-gantt until I tracked it back to an addon. For example: ember-truth-helpers and ember-decorators

The scale bug: the scale bug You see here that blueberries are going off the right side. This is because I was calculating the scale based on the last plant in the list, which isn’t always the one that will be harvested last, only the one that will be planted last.

Instead, I had to use d3.max and offset to create a custom function to look at all of the plants and see which one would be harvested last:

let lastToHarvest = maxIndex(sortedData, d =>   timeDay.offset(new Date(d.start), d.daysToMaturity) ); 

Today’s commits:

  1. upgrade ember, add form components, add some helpers
  2. updates to chart with form data
  3. get the color picker working
  4. fixed end data calculation bug

Here’s how it works so far!

plant gantt with forms

Tomorrow’s goal:

  • Improve the form UI
  • Use D3’s update() to update the chart instead of destroying and redrawing it
  • Show a different screen on the first time you land instead of showing the mocked out chart
  • Break the components apart

Advent of Code, Day 4: Responsive D3 Chart in an Ember Component

Day 4!

Today’s progress

  • Sorted vegetables by start date
  • Updated the data model to calculate the harvest date by the days to maturity
  • Changed the scale to start 15 days before the first planting and end 15 days after the last harvest to save space on screen. This also allows things to start before January and end after December.
  • Made the chart responsive

Making the chart responsive took me down a weird rabbit hole. I tried using a computed property and then a bunch of different addons, but none of that worked. So I went the old-school route and wrapped the chart drawing in a function and then added an event listener to remove the chart and redraw it. There is probably a more elegant way to handle it, but I don’t know what it is.

window.addEventListener('resize', function() {   selectAll('svg').remove();   drawChart(); }); 

Today’s commits:

  1. change tick format to months and sort data
  2. move to relative scales
  3. responsive chart

Tomorrow’s goal:

  • Add a form to add new elements and update the chart

Advent of Code, Day 3: D3 chart basics

Day 3!

Today’s progress

  • set up pods because that is what I’m used to working with at Crash
  • configured Tailwind in Ember
  • successfully rendered yesterday’s D3 mock inside a component
  • Talked to my first user (my wife!) and got feedback about the chart. She says vegetables should be sorted by when you plant them by default, not when you added them to the chart.

Resources that helped me:

Today’s commits:

  1. configure tailwind, move to pods
  2. add calendar component and add D3 mock to it

Tomorrow’s goal:

  • change the calendar component to be responsive
  • sort the vegetables
  • Change the data to be closer to user-input data (plant date and duration, not just plant and harvest dates
  • watch more embermap videos to learn how to update with changes to user input data

Advent of Code, Day 2: D3 chart basics

Day 2! I woke up excited to work on this and spent an hour on it before work.

Today’s progress

Figured out the basics of the D3 implementation for Plant Gantt:

  • Learned how to use Observable
  • The week-based X axis
  • Stacking each plant similar to a horizontal bar chart and starting the bar at the correct start date
  • Lining the labels up correctly

Here is the Observable notebook.

Tomorrow’s goal

Get back into Ember land and figure out how to incorporate a chart with mocked out data in an Ember component.

Advent of Code, Day 1: Picking a Project

I’ve wanted to do Advent of Code for a few years, but I just can’t get excited about random puzzles. Not knocking all the folks involved in it–more power to you! It just isn’t for me. So I haven’t participated.

Last week while flipping through a seed catalog to figure out what I’m going to plan in my garden next year, I had an idea: I want to build a plant starting/harvesting calendar for my garden. Building that will be my own Advent of Code project this year.

Today was Day 1, planning day.


Plant Gantt. Amanda came up with it and it is perfect. A gantt chart for plants.


  1. Build an interactive planting/harvesting calendar for vegetables.
  2. Deploy my own Ember app with a user login system on something like EmberFire
  3. Beef up on my D3 and Ember skills.
  4. Use Tailwind for the first time
  5. Make progress every day.


  • Ability to add a plant name, seed start date, days to maturity, and pick a label color for it.
  • Visualize the plant’s growing period on a 52 week calendar
  • See the current day marked across the full calendar
  • Ability to hover over a plant and see how long it has been growing and how long left until harvest
  • Ability to create an account and save your calendar
  • Ability to print the calendar you created
  • Ability to create multiple calendars with unique names

I may not get this done in 25 days, but I intend to make progress every day. It will be tough with the month ahead: Two out of state trips, moving into our new house, and holiday parties. I’ll carve out time every day to get some work done, though.

Today’s progress

  1. Picked a name and got a domain
  2. Planned out the features and my goals for the project
  3. Set up the git repo and basic Ember app
  4. Install add-ons: ember-d3, ember-cli-tailwind

Tomorrow’s goal

  • Figure out the basics of the D3 gantt-style chart

How to download inline SVGs

I find myself regularly scouring business websites for logos to use. More and more of them use build pipelines that take all SVGs and embed them inline, making it impossible to just right click and download the image.

Here is my short workflow for downloading inline SVGs:

  1. Open up your inspector and copy the whole SVG element.
  2. Open your favorite text editor (mine is VSCode) and paste the whole SVG element you just copied into a new file and save it as companyname.svg
  3. Open your new companyname.svg in Sketch.app and change the colors if needed. Sometimes the colors are applied via CSS, which won’t come in with the SVG element.
  4. Export the logo as your filetype of choice. Mind the size because inline SVG elements are often small. I usually export PNGs at @2x.

Note: I still use steps 3 and 4 even if my end goal is SVG. First, I want to make sure the colors are correct. Second, Sketch adds some important attributes to SVGs like Viewbox that are helpful if you are using the file on the web.

Here is a video I made walking through the process:

Building a reading list with Jekyll data files

I transitioned by previous book notes section to a new reading list section. Why?

  1. I read a lot of fiction and don’t tend to take notes on it
  2. I wanted to keep a list of books I’ve finished.
  3. The book notes was not a great medium for doing #2. Writing the full set of notes was a pretty big barrier most of the time and I tended to go a few months at a time without updating the page, creating a large backlog.
  4. I came across Frank Chimero’s reading page and loved it. I also noticed that James Somers takes a similar approach with just listing the books out.

So, I decided to implement Frank Chimero’s design with my own reading list.

Instead of just hardcoding them in a page and repeating tons of HTML for the rest of my life, I decided to put the reading list in a Jekyll data file (I chose yaml) that I could easily add to and make a simple template to output.

First, I started with my previous book notes section. Since they were stored in a Jekyll Collection, I was able to write a quick Liquid for loop to generate most of my list for me. But I didn’t want to: include a full star rating, just a recommended star. So I chose to star books that I rated 5/5:

{% assign reviews = site.book_reviews | sort: "date" | reverse %} {% for review in reviews %}  - title: "{{ review.title }}"   author: {{ review.author }}   link: {{ review.book-link }}   star: {% if review.stars == 5 %}yes{% else %}no{% endif %}  {% endfor %}

The result looked like this:

- title: "Stories of Your Life and Others"   author: Ted Chiang   link: http://amzn.to/2Ghql7a   star: yes  - title: "A Burglar's Guide to the City"   author: Geoff Manaugh   link: http://amzn.to/2rElHgb  - title: "The Story of Sushi: An Unlikely Saga of Raw Fish and Rice"   author: Trevor Corson   link: http://amzn.to/2DAvzct  - title: "The Island at the Center of the World: The Epic Story of Dutch Manhattan and the Forgotten Colony That Shaped America"   author: Russell Shorto   link: http://amzn.to/2yqLmaE   star: yes 

Awesome! That spit me out a list of everything I’ve written book notes for. Since it is sorted by date in reverse chronological order, it was quick for me to manually break it up by year:

 - year: "2019"     books:       - title: The Summer Book         author: Tove Jansson         link: https://amzn.to/2ZMtUuC         star: yes        - title: "The Last Pirate of New York"         author: Rich Cohen         link: https://amzn.to/34h0Oa3         star: yes        - title: "Fall; or, Dodge in Hell"         author: Neal Stephenson         link: https://www.amazon.com/Fall-Dodge-Hell-Neal-Stephenson/dp/006245871X         star: yes  - year: "2018"     books:       - title: "The World is a Narrow Bridge"         author: Aaron Thier         link: https://amzn.to/2PK3OZc        - title: "Fashion Climbing"         author: Bill Cunningham         link: https://amzn.to/2PJqkgz        - title: "Cape Cod"         author: Henry David Thoreau         link: https://amzn.to/2ZIISBV        - title: "The Outermost House"         author: Henry Beston         link: https://amzn.to/2MOf3hb         star: yes 

Excellent. Two more steps:

  1. I want a “last updated” key so I only have to update one file. That means I need to nest the years under a parent key in order to access them.
  2. I need to manually add the books that weren’t in my notes but I know I read. This took me a few hours to get the books, authors, and links.

Once I added the “last updated” key, the yaml structure looked like this:

lastupdate: September 3, 2019 list:   - year: "2019"     books:       - title: The Summer Book         author: Tove Jansson         link: https://amzn.to/2ZMtUuC         star: yes        - title: "The Last Pirate of New York"         author: Rich Cohen         link: https://amzn.to/34h0Oa3         star: yes        - title: "Fall; or, Dodge in Hell"         author: Neal Stephenson         link: https://www.amazon.com/Fall-Dodge-Hell-Neal-Stephenson/dp/006245871X         star: yes        - title: The Boatbuilder         author: Daniel Gumbiner         link: https://amzn.to/2Lgvh0l    - year: "2018"       books:         - title: "The World is a Narrow Bridge"           author: Aaron Thier           link: https://amzn.to/2PK3OZc          - title: "Fashion Climbing"           author: Bill Cunningham           link: https://amzn.to/2PJqkgz          - title: "Cape Cod"           author: Henry David Thoreau           link: https://amzn.to/2ZIISBV          - title: "The Outermost House"           author: Henry Beston           link: https://amzn.to/2MOf3hb           star: yes 

Here’s how I loop through that data to create my reading page:

{% for entry in site.data.reading.list %}    


{{entry.books | size}} books
{% endfor %}

Note that {{entry.books | size}} line: This is what outputs the number of books I’ve read in a particular year by spitting out the size of the books array. This is one of the big benefits of using a data file instead of hardcoded HTML or markdown. Just add a new book and everything else updates itself. Plus, I can use the data file for other things later if I so choose.

Here is the result:

 reading list

You can check it out over at the reading list page, or you can see the code over at my Jekyll Tools Github repository.

If you liked my book notes, don’t worry. They aren’t going anywhere. I’m keeping the pages up for posterity, but I’m not updating them any more.

Adding Homebrew MySQL Service to your PATH

If you install mysql via Homebrew ($ brew install mysql) and start it via Homebrew services ($ brew services start mysql), chances are that you’ll see an error when trying run mysql -u root on the command line.

If that error is command not found: mysql, the issue is likely that you need to add the Homebrew mysql directory to your PATH.

On my machine, Homebrew installed mysql here:


To add this to your PATH, add this to your .bash_profile (or .zhsrc if you use ZSH):

export PATH=/usr/local/Cellar/mysql@5.7/5.7.25/bin:$PATH 

Then, reload your shell and type mysql -u root to confirm all is working.

You can quit the mysql CLI via mysql> q

Getting around foreign key constraints when restoring a Craft CMS database

Are you getting foreign key constraint errors when trying to restore Craft CMS database backups? Here is how to solve it.

Every time I download a backup of a Craft 3 database and try to restore it to my local development environment, I get this error: Cannot add foreign key constraint

This happens because the SQL dump has the data in a different order than it should be loaded in according to the foreign key constraints.

The solution is to use a MySQL session variable to turn off foreign key constraints and be able to load your data in the order it is currently in.


WARNING: Do NOT even think of doing this in your production environment. Only development. You’ve been warned.

Open your .sql dump in your favorite text editor and put this line at the top:


Save it, then retry your import. Works for me every time.

I wrote this post so that I don’t have to search how to do it in six months when I need to do it again.

p.s. if you want to do something similar in postgres instead of mysql, search for SET CONSTRAINTS ALL DEFERRED.

How to Upgrade from PHP 5.6 to 7.2 for WordPress

Last week I updated three large WordPress sites for Praxis from PHP 5.6 to 7.2. Here is the process I used on all three to make it a smooth transition.

Note: I do contract work and can help you figure this upgrade out. Reach out if you are interested in getting a quote.

1. Figure out the scope of the problem.

First you need to know how much time you should plan to spend doing this work. A quick way to find the scope of the problem is to run the PHP Compatibility Checker plugin. It checks your themes and plugins, spitting out errors, warnings, and filenames/line numbers of the offending code so you know where to start.

  • Small theme upgrades?
  • Plugin updates?
  • Massive refactoring of custom code?

Even if you get a 100% clean pass with the compatibility checker, I suggest going through the rest of my list. The compatibility checker isn’t perfect. You don’t want to roll those downtime dice. Fool me one time, and all that.

2. Test the upgrade locally.

Copy down your current theme and database into your local development environment. This is important. Don’t use the version you have from work you did a few months ago. Stuff changes on the server all the time. Pull down a copy to be safe. Configure your local environment to use PHP 7.2. If you are unsure whether or not you are on 7.2 locally, you can always use a WordPress plugin like Debug Info to check. Also make sure to turn on error reporting in your php.ini.

Don’t have a local development environment yet? I like Valet.

While you are at it, I suggest upgrading your WordPress core to the latest version (5.1.1 as of this writing) and updating all of your plugins. Rip that bandaid off. On the plus side, it will probably make the PHP upgrade process easier, too.

3. Find, diagnose, and solve any issues.

This is either the easiest or hardest step. If your site looks fine and everything functions as it should, great! You won the lottery.

It is probably worth double checking that you actually are on PHP 7.2 with the debug plugin mentioned above. Also worth clicking all around your site and checking everything. Sometimes most of the site will load, but you’ll get weird inline error messages like this:

Warning: Use of undefined constant Y - assumed ‘Y’ (this will throw an Error in a future version of PHP) in /nas/content/staging/creativecourse/wp-content/themes/business-pro-theme/page-archive.php on line 70 

If you navigate to the site locally and all you see is a sea of errors like I did on one site, you have some work cut out for you. Here are some tips:

  • If the errors include wp-includes or wp-admin in the file paths, you need to update your WordPress core.
  • If the errors include wp-content/themes/ in the file path, and you don’t have any custom work done on the site, the quickest fix is to upgrade your theme or find a new one.
  • If the errors include wp-content/plugins/somepluginname/ in the file path, find that plugin and disable it or update it and try again. You should still be able to get to your /wp-admin/ dashboard even with a broken site because the most recent core works with PHP 7.2.
  • If your site includes a bunch of custom code and the errors are happening with the custom code, you have more work to do. If you are comfortable debugging and fixing it, great! If not, you might need to call the person or company who wrote it. Or decide if you can do without it. Hopefully you followed conventions and put it in a child theme.

Keep track of the changes you make. I keep a running list in my favorite notes app. Theme updates, core updates, plugin updates, and code changes. Write it all down. Better yet, version control it with git and commit along the way.

If you have any premium plugins that require activation keys, make sure that you contact the developer to get two extra development keys for local testing and production. Most will provide those to you at no extra cost. Some platforms give you 5 keys per purchase anyway just for this reason. I decided to roll the dice and not get keys for one plugin, and ended up crashing my production site with the upgrade because I didn’t test that plugin. Don’t be like me.

All three of my sites needed some work done, even the newest of the three. One didn’t load at all until I made some theme updates. One plugin crashed a site and had no updates available, so I had to do without. Some custom code I had written needed updates, too. It took me a few afternoons to sort it all out.

4. Deploy to a staging environment on PHP 7.2 first.

Once you have everything sorted out locally and you’ve triple tested everything, go ahead and push your work up to a staging environment. Good hosts like WPengine make having a production and staging environment easy. You can control the PHP versions independently. I use WPengine’s git deployment to move my local updates to the staging server. SFTP works, too. If you need database changes, you’ll also need to do a database migration. Core and plugin updates will need a database migration (Don’t forget to change the site URL or you won’t be able to log in!) If you need to do a database migration, use the instructions here

My preferred workflow is to copy the full production site (code + database) to staging, push my code changes, then do the core and plugin upgrades on the server (they often involve database changes), which is when my change list above comes in handy. I don’t like doing database migrations unless I absolutely have to, because with lots of users logging in to the site, data is bound to get out of sync with my local version in a matter of hours. Getting back in sync is tricky, so I choose to avoid the problem altogether by only moving code, keeping my change file, and replaying the steps on the staging server.

If you don’t have a staging environment and can’t get one with your host, or you can’t upgrade the PHP version yourself on your host, then you need a different plan. You’ll need to set up another site on another host that uses PHP 7.2, then put the updated copy of your site there. If you do this,proceed to the alternate #5 below.

Don’t assume everything works. Test it! Use different user account levels (admin, editor, author, contributor, and subscriber). Try every critical function on your site.

Only move on to the next step when you are certain everything works like it should.

5. Deploy to production.

Before you do anything else here, make a back up of your site and your database. Good hosts like WPengine make this easy. If you are doing this manually, verify the backups work locally. You can to be able to roll back if there is an issue.

I planned a maintenance window with my users a few days ahead of time. If you have users who rely on the site, you should, too.

Update your production environment to PHP 7.2, then copy the staging server to the production server. If you are confident that there were no users updating content in between the staging copy above and now, you can copy database and all. If there are changes you don’t want to lose on the production database, you’ll need to use my method and only copy over the theme code, then do core and theme updates on the production site. This is when planned downtime comes in handy.

Alternate deploy (no staging)

If you set up a separate site on 7.2 (not a staging environment of a production site) and it works, then you are ready to cut over to the new version. Configure that new site to use your domain (URL field on the setting page), then point your domain’s DNS records at the new server. If you need to do a database migration, use the instructions here.

6. Verify everything works

Don’t assume everything works. Test it! Use different user account levels (admin, editor, author, contributor, and subscriber). Try every critical function on your site. Use the Debug Info plugin to verify that the PHP change took.

If there are any issues, you might need to roll back to an earlier restore point and PHP version (or point your domain back to your other server), or do some real-time debugging. If your production and staging servers mirror each other in every way except PHP version, you shouldn’t run into anything unexpected. If you do, figure out where the difference is and go back to #3 above.

Moving from Homestead to Valet for WordPress Development

I used to use Homestead for local WordPress and Craft development. It was nice because everything worked out of the box. Well, theoretically. In reality, it worked about 80% of the time, but 20% of the time I’d get cryptic errors from Vagrant like:

  • Some box with the same name exists. We won’t tell you where, but trust us, it exists and that can’t happen.
  • The SSH command responded with a non-zero exit status. Vagrant assumes that this means the command failed. The output for this command should be in the log above. Please read the output to determine what went wrong.
  • Error! You have to destroy this box and rebuild it. Hope you backed up those local databases, because they are gone forever!

It was always a rabbit hole of headaches that could take half a day to resolve. Most resolution happened in the form of me destroying a box and rebuilding it, losing my databases. So much fun.

Today I got the SSH error again and had enough. I went looking for a better solution and found Valet. Their pitch:

Valet is a Laravel development environment for Mac minimalists. No Vagrant, no /etc/hosts file. You can even share your sites publicly using local tunnels.

Sounds great. It won’t eat my battery, RAM, CPU, or take gigs of disk space for vitual machines. Sign up the heck up.

It was super easy to set up with tools I already use: Homebrew and Composer. Instructions here.

The only tricky part was making composer directory available in my Mac’s PATH, since I’ve only ever used it inside individual projects. Turns out with zsh you have to give the explicit path, you can’t just use ~/, so I added this to my .zshrc: export PATH=/Users/cagrimmett/.composer/vendor/bin:$PATH

Once I loaded the profile with source ~/.zshrc, I was good to go. I linked my main projects folder so Valet knows where to look for projects, changed my wp-config.php files to point at my local mysql database instead of Homestead’s, then navigated to project-folder-name.test and was good to go!

Valet runs at startup, so I don’t have to run cd ~/Homestead && vagrant up anymore, just brew services start mysql@5.7 if I need a database running (which you usually do for WordPress development.)

I like that Valet makes it so easy to use different PHP versions. For example, to use 7.2, you go to your command line and type: valet use php@7.2

I’m looking forward to never using Vagrant or VirtualBox again. Peace out

Automated Accountability Check-ins

The Need

During the Praxis bootcamp, participants are expected to make every single day a non-zero day. Most participants ask us to hold them accountable, so they email one of our staff each day and that person emails them the next day if they miss a check-in. This takes an enormous number of emails, staff overhead to keep track of, and is difficult to search. Isaac asked me, “What can we do to automate this?”

It needs this functionality:

  1. The ability to submit what you’ve worked on that day.
  2. The ability to see what you’ve submitted.
  3. The ability for staff to see what you’ve submitted.
  4. The ability to send an email to people who didn’t submit a check-in the previous day.
  5. The ability for logging who missed a check-in.

The Restrictions

There are existing systems that do this sort of thing, so why make our own? During the bootcamp, our customers use what we call The Portal, which is a curriculum platform I built on top of WordPress with the help of Restrict Content Pro and wpcomplete. I didn’t want to add another destination into the mix. I wanted a reason for customers to go to the Portal every day and keep their work moving forward. Plus, they already have logins there. No need to add yet another login for them to keep track of.

The Solution Sketch

  1. USP Pro for front-end post submission
  2. Custom page template to display the content box but hide the tedious stuff: User ID, today’s date, user email
  3. New WordPress user meta field for keeping track of accountability opt-ins
  4. Custom script to check if opt-ins have posted within the last 28 hours and fire off a webhook to Zapier to send the email and log missed check-ins to a spreadsheet
  5. Zapier time trigger with a GET hook to run the script at a specific time to kick off the accountability check
  6. Custom template to display all opted-in participants and their check-ins to make it easy for staff to see everything in one place
  7. Hide the check-ins from search results

The Solution Details

Front-end post submission and showing posts

USP Pro handles all sorts of complexity when it comes to submitting posts from the front-end of a WordPress site: The form, the custom post type, assigning the current logged-in user to be the author, and displaying success and error messages. They also have hooks and filters to make using the posts in templates easier.

I used a custom template to hide some of the things I’d need to make the whole process work: User ID to assign as the post author, name and today’s date for the title of the post.

After the form, we also want to show all the posts someone has already submitted. I do that with a WP_Query_  Note: I added a Delete button here in case some makes an accidental post. If you want someone to be able to delete their check-in post, they need to have the role Author.

Here is the code I used for the template and comments about what it does:

// Check if user is logged in. Display the check-in form if so, login form if not. if ( is_user_logged_in() ) { //Get the logged in user, today's date, and the PHP session ID 	$current_user = wp_get_current_user(); 	$today = date("F j, Y"); 	$ses_id = session_id(); ?>    class="usp-pro-form">  	 	 id="usp-pro" class="usp-pro usp-form-4102"> 	 id="usp-form-4102" class="usp-form" method="post" enctype="multipart/form-data" action="" data-parsley-validate="" data-persist="garlic">  		 class="usp-fieldset usp-fieldset-default"> 			 for="usp-content" class="usp-label usp-label-content">

What did you do to make today a non-zero day?

name="usp-content" id="usp-content" rows="5" cols="30" maxlength="999999" data-required="true" required="required" placeholder="Today I..." class="usp-input usp-textarea usp-input-content"> type="hidden" name="usp-content-required" value="1"> class="usp-hidden"> // Get first and last name, today's date, set that as the title. echo '. $current_user->user_firstname . ' ' . $current_user->user_lastname . ' ' . $today . '" type="hidden" />'; // Get user ID for author echo '. $current_user->ID . '">'; // Get PHP session ID. Plugin thing. echo '. $ses_id . '">'; ?> type="text" name="usp-verify" id="verify" value="" style="display:none;" class="exclude"> type="hidden" name="usp-form-id" value="4102">
echo do_shortcode('[usp_form id="submit"]'); ?>

Your submitted check-ins:

// Show posts from this user ID with the type usp_post $post_query = new WP_Query( array( 'post_type' => 'usp_post', 'author' => $current_user->ID, 'orderby' => 'date', 'order' => 'DESC', ) ); if ( $post_query->have_posts() ) : while( $post_query->have_posts() ) : $post_query->the_post(); ?> post_class(); ?>>

the_time('F j, Y \a\t g:i a') ?>

the_content(); ?> href=" echo get_delete_post_link(); ?>" style="font-size: .8em;">Delete this check-in
endwhile; else: echo "

You have no check-in posts. Submit one above!

; endif; ?>