I updated my #Docker + #WordPress containers to build a custom 🔌 #plugin for a client
Category: Plugins
WordPress + Docker
Dear Vagrant,
You’ll always hold a special place in my heart but there comes a time when we have to put the past behind us. We grow, change, and you and I aren’t what we used to be. We’ve grown apart and become so different. There is no doubt that someone out there will love you, but for me; well I’m done with the days of an upgrade to VirtualBox breaking my virtual environment. I’m saying goodbye to your virtual machines taking a whopping 7 seconds to start. Vagrant, I’ve found someone else, and yes; it is Docker.
💔
If you’ve read any recent posts of mine, you’ll have noticed a distinct lack of information regarding WordPress. It’s not that I dislike WordPress now but given the option of developing with WordPress or not-WordPress, I’d choose not-WordPress. Remembering all of the hooks, the cluttered functions.php files, and bloated freemium plugins has all become so tiresome. That’s not to say that I’ll never work with it again, after all; this blog is powered by WordPress as is more than 1/3 of the web as we know it. Since I’ll never officially be done with WordPress, I should at least find some more modern ways to manage development with it.
Dockerize It
To get WordPress development environment up and running quickly, I use docker-compose. I’ve written about it before over here. It’s incredibly simple since the docker community maintains a WordPress image.
docker-compose.yml
services:
db:
image: mariadb:10.6.4-focal
command: ‘–default-authentication-plugin=mysql_native_password’
volumes:
– db_data:/var/lib/mysql
restart: always
environment:
– MYSQL_ROOT_PASSWORD=somewordpress
– MYSQL_DATABASE=wordpress
– MYSQL_USER=wordpress
– MYSQL_PASSWORD=wordpress
expose:
– 3306
– 33060
wordpress:
depends_on:
– db
image: wordpress:latest
ports:
– “8000:80”
restart: always
volumes:
– ./:/var/www/html
– ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
environment:
– WORDPRESS_DB_HOST=db
– WORDPRESS_DB_USER=wordpress
– WORDPRESS_DB_PASSWORD=wordpress
– WORDPRESS_DB_NAME=wordpress
volumes:
db_data:
For the most part, this file is the same as the one taken from the docker quick start example. Here are couple directives that I made changes to:
ports: I changed these around as I had some other services running from different programs
depends_on: Telling WordPress not to start until the DB has started also prevented some issues where I was seeing the famous “white screen of death”
volumes: set the current working directory to be the WordPress instance as well as created a custom PHP ini file to fix upload size restrictions
uploads.ini
file_uploads = On
memory_limit = 1024M
upload_max_filesize = 10M
post_max_size = 10M
max_execution_time = 600
Placing this uploads.ini file in a directory accessible by the docker-compose.yml let me fix problems with large file uploads.
I got 99 problems and they’re all permissions
This is all well and good but there are some issues when trying to develop locally. For instance, after running docker-compose up -d, I noticed that all files belong to www-data:www-data. This is necessary for the web server in the container to serve the files in the browser at http://localhost:8000. But then I didn’t have write permission on those files so how could I manage them?
I came across a couple of solutions but they each have their drawbacks. For instance, if I set the entire WordPress instance to be owned by my user, then the web server won’t have permission to read or write to files. I could also add my user to the group www-data but even then, I won’t have write permissions until I run something akin to sudo chmod 764 entire_wordpress_dir which isn’t desirable (but is probably the best option so far). The compromise I came up with was setting myself as the owner for all of the WordPress install, and giving WordPress ownership of the uploads directory. It seems that for now, I’ll just have to flip permissions via the CLI.
If you have ideas on how to resolve the permissions issue with WordPress and docker-compose, leave a comment below!
Recently, I was tasked with adding a custom field to a WooCommerce order details section. This itself is not a major task and is well documented online, but this particular field had some special requirements. It had to
Allow users to select a date for delivery/pickup
Restrict possible order dates to days when the facility is open
Sounds simple enough, right? Just toss a calendar widget in there and limit the days to weekdays since weekends and holidays are the only days when this particular facility is closed. Weekends are easy, but holidays? That’s where it got a little bit weird. Take Labor Day for instance. It always falls on the first Monday in September. President’s Day falls on the third Monday of February each year. These wouldn’t be difficult to code for the current year, but we don’t want our calendar widget to only work for this year. It should work for every year, so we don’t have to remember to go update our code every December 31st.
This company also has special rules regarding holidays. For example, if the 4th of July falls on a Saturday, then they are closed the Friday before. If the 4th is on a Sunday, then employees get the following Monday off. All in all, there are 11 different holidays we need to prevent orders from happening on.
I wasn’t sure how this could all be done with jQuery, so I ended up doing it in PHP and then serving up an array of dates to be blocked off in the widget with an AJAX call.
PHP
In our PHP code, I went through the normal suggested steps of enabling AJAX calls in a plugin and ended up with the function `company_holidays_callback`. This function gets our current year, and pushes each holiday onto an array. It does this for the current year, and the following year. Figuring out the date for each holiday wouldn’t have been so simple without the PHP strtotime function. That was a life saver! And the function `fri_or_mon` was the logic used to mark the previous Friday or the following Monday off of the calendar as per special rule of this particular company.
jQuery
To break down the jQuery, we create the instance of our datepicker on the element `#order_fulfillment_date`. If that element exists on the page, we perform our AJAX call to get the list of dates that need to be restricted. The function `noWeekendsOrHolidays` checks to if the date is a weekend. If it is, it gets disabled. If it isn’t, it passes the day onto `nationalDays` where we check if the date is one of our holidays that needs to be disabled as well.
In all of this code there isn’t really anything special or groundbreaking, but I thought the logic that determined the holidays was useful and maybe someone else could save themselves a little bit of time and headaches. Thanks for reading!
FTP sucks. I mean, it’s fine. Whatever. But moving code with FTP sucks. You have to make sure you’ve downloaded the latest version, and you have to be careful not to overwrite or delete other files. One missed click, and you can have a long evening ahead of yourself. Because of the issues I’ve had with FTP in the past, I’ve been on the lookout for better ways of getting my code to my applications. You might remember that I previously posted about deploying a web app using Capistrano and while Capistrano is really cool, I still suck at Ruby. So when confronted with yet another deployment problem, I wanted to use Capistrano, but I didn’t want to have to butcher someone else’s deployment recipe again. And I really, really didn’t want to FTP my code up to the server again.
In comes Git; not just for version control. You’re already using it to keep your code safe. Why not use it to securely push your code to your server? In my case, I needed to push a plugin that contains custom features. So how do you use Git to deploy your code?
I’m going to operate off of the assumption that you’ve already got your code in a repository. If you haven’t done that, do it now. Once you have done that, then SSH into your server and create directory to store a remote repository.
ssh [email protected]
mkdir repo_title && cd repo_title
git –bare init
You’ve now got a bare repository to host your code. The next step deals with the hooks in the repo.nano hooks/post-receiveIn that file, drop this code:
#!/bin/sh
GIT_WORK_TREE=/var/www/public_html/wp-content/plugins/custom_plugin git checkout -f master
Obviously, change that path to whatever the path is to your plugin location on the server, save it, and then run:chmod +x hooks/post-receiveso that your code has execute rights.
Now exit your SSH connection, and go back to your local repository. You’ll now need to create a remote in your local repository for your server.
cd your-project
git remote add myserver ssh://[email protected]/~/repo_title
git push myserver +master:refs/heads/master
To deploy your code, just rungit push myserverAnd that’s it! Now, you can move code without ever having to write a Ruby script or open an FTP client.
Thanks to Matt Banks over at mattbanks.me for his excellent write-up. I recommend you go check that out seeing as how that’s where I found this.
Source: Managing WordPress Theme Deployments with Git – Matt Banks
Update: It’s become popular to rename the master branch to main. If that is the case for you, simply replace all instances of “master” in the code with the branch you would like to use.
As far as development goes, it feels like if you aren’t using Vagrant, you aren’t doing it right. And if you’re doing WordPress development (themes, plugins, or even Core), you’re behind the curve if you haven’t started using VVV (Varying-Vagrant-Vagrants). VVV let’s you spin up another development instance relatively easily, but apparently, it can be even easier with vv (Variable VVV).
Damn, that’s a lot of V’s.
But seriously, this tool makes it even easier to create another development instance. If you haven’t checked it out already, I definitely recommend it. Use the command create to spin up another instance, and get a few questions about the kind of instance you want to create. This stuff is magical.
Over the past weekend, I had the pleasure of attending WordCamp Minneapolis 2015 and it was a blast. You can check out my twitter feed (@awebdevguy) for some pictures of all the swag I scored. I basically got a new wardrobe from it, so tickets are definitely worth it.
But anyways, back to what this post is actually about.
Plugins are great. WordPress is great. Sometimes though, developers make mistakes. If you’ve followed my blog at all, you’d know that I just created a plugin recently, and the entire time, all I could think about was security. Recently, some big security issues have come up in WordPress and some big name plugins (Yoast SEO, Gravity Forms, etc). The thing that’s scary about this, is that these guys are the pros. Even the pros make mistakes, so how likely is it for an amateur developer like me to slip up? Probably much more. So while at WordCamp, the very first talk that caught my eye was from John Havlik titled Writing (More) Secure Plugins. Obviously, I would like to know how to do that. Every WordPress developer should, and John does an excellent job of explaining not only why you should care, but how you can remedy the most common issues of security. His presentation can be found here so definitely give that a glance.
WordPress Is A Big Fat Target
That’s one of the problems of being king. It’s not that WordPress is inherently insecure. It’s just that it has such a large market share. Think of it like the conversation that inevitably comes up when discussing PCs vs Macs. Someone always says “Macs don’t get viruses.” which is blatantly false. They are just as prone to viruses as PCs but PCs just make for a bigger target. WordPress is the same way. It is estimated to make up ~23.8% of all sites on the web! Why wouldn’t you target that as a hacker?
And since there are so many WordPress sites, it’s not uncommon to find poorly developed plugins and themes. That’s why we, as developers, have a responsibility to make our plugins more secure. It isn’t entirely the developers fault though. PHP has to take some of the blame. PHP is very dynamic and weakly typed. Variables can be created on the fly, and you can’t really prevent a variable from suddenly becoming a string. Strings open us up to vulnerabilities with our HTML output, form input, and database queries.
XSS
XSS (cross-site scripting) is probably one of the most common and easy ways to get into a site. It looks like this:
<a href=”<?php echo $_SERVER[‘REQUEST_URI’];?>”><?php echo $title;?></a>
The issue here is the $_SERVER[] request being echo’d out immediately. If someone were to discover this loophole, they could drop whatever code the wanted in the URL like so:
http://my-wp-site.com/?”><script>alert(‘Porkchop Sandwiches!’);</script>
Now if someone really knows their way around WordPress or your site, they could inject any number of things there, and execute their own code on your server. So how do you prevent this? You almost have to stop anytime you’re accepting input from the client, and make sure it’s validated. Check that it lands between an expected range, that it is a certain type, and only allow specific characters. This will all help protect your plugin. John suggests that you sanitize and validate your inputs as early as possible. Using functions like intval(), absint(), and isset() will help against invalid types and values. Functions like sanitize_email(), sanitize_text_field(), and sanitize_file_name() will clean your inputs, but be warned, they might change values that your users have submitted. Be sure to check out John’s presentation for more examples.
SQLi
SQLi (SQL injection) is nasty. We’re talking, all of your posts, comments, data, tables, etc. gone in one fell swoop. If you’re developing a WordPress plugin, use the WordPress API. The functionality is there, and most of the leg work is already done for you. Plus, it’s more secure. If you do have to access the database directly, WordPress has you covered there too. By using the $wpdb object, you get access to $wpdb::insert(), $wpdb::update(), $wpdb::delete(), and $wpdb::replace(). Those should be able to handle just about everything database related. If you are writing SQL, then use $wpdb::prepare() to make sure your query is safe to run.
When it comes to getting data back from your database, you can’t trust it. Data needs to be escaped as late as possible, cause who knows what our users entered in there. By using esc_html(), esc_attr(), esc_url(), and esc_js() we can prevent more problems.
Unsafe:
<a href=”<?php echo add_query_arg( ‘foo’, ‘bar’ ); ?>”><?php echo $title;?></a>
Safe:
<a href=”<?php echo esc_url( add_query_arg( ‘foo’, ‘bar’ ) ); ?>”>
<?php echo esc_html( $title ); ?>
</a>
CSRF
Cross-Site Request Forgery is a combination of hacking and social engineering. Basically, by tricking someone who is already logged into your site to click on a malicious link, an attacker can gain that user’s access. That link could be embedded in a comment or an email. To prevent this from happening to your users, use a nonce. Nonces are one time use tokens that are used for protecting links that execute an action. For instance, if I want to create a link that deletes a user, I don’t want that link to be available to anyone. We want it protected so that it can’t be emailed off and clicked by another user. Nonce’s look like this:
wp_nonce_url( $actionurl, $action, $name )
$actionurl is the URL we want to protect, $action is our action name, and $name is our nonce name. At this point, I’d recommend checking out John’s presentation again, just because he does such a good job of explaining nonces. Also, read up on the WordPress Codex.
So to sum this up, don’t trust external inputs (including the database), validate early, and escape late. Those are some of the big things to keep in mind when developing a plugin. It’s also a good idea to never trust the contents of $_SERVER, as it could be manipulated by the client. And did you know is_admin() doesn’t check if the user is an admin? It checks to see if the dashboard or administration panel is attempting to display. So don’t try locking your admin specific features down with that. Aside from that, stay away from the PHP functions eval() and assert(). You’re just asking for trouble by using those.
If you use WordPress regularly, you know that it is often the target of hacking attempts, and rightfully so. It accounts for nearly a quarter of all websites! Why wouldn’t a hacker want to target WordPress? That, plus the plethora of amateur developers releasing plug-ins and themes with gaping security flaws, makes WordPress an easy win for someone with malicious intent.
You probably already know about the vast list of security plug-ins, that you shouldn’t write down your passwords, that you should use different passwords on every site, etc. I’m not going to list all of those things, because it’s boring and repetitive. I am going to tell you about WPScan though. WPScan does exactly what it sounds like. Scans your WordPress site. Boom. That simple. It’s backed by the guys over at Sucuri so you know it’s legit.
Do yourself a favor and check out WPScan either on their website or just go to the Github repo and clone it. It doesn’t work on Windows, so sorry about that. But it should work with a Mac or Linux machine. The install is pretty simple too. Here’s what I did:
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install –without test
Also, it’s nice to set an alias so you don’t have to be in that directory to run it all the time:
alias wpscan=”ruby /home/USERNAMEHERE/Documents/wpscan/wpscan.rb”
That way, you just run wpscan –update or whatever command you want and it works. It’ll give you a nice big list of things that you should look into, and tell you a few things you probably didn’t know.
I’ve made a few changes to the Edit Hopper WordPress plugin that I developed and I’m glad to say that it now supports Custom Post Types and Posts. Plus, thanks to Jimmy (@sporifolous on Github), it now has an options page to select which posts you want the meta box to show on. So if you’ve got a WordPress site, check it out.
https://github.com/Dilden/Edit-Hopper
Big shout out to @socki03 with his help on cleaning up some code.
A long, long time ago, I worked on a website that utilized an almost insane amount of pages and child pages in WordPress. While I was adding content to those pages, I realized that I hated having to click the Pages link in the sidebar of the WordPress admin to go back, and then dig through all of the pages, then click edit, and type in my content. I wanted things to be easier. No, they should have been easier, and much simpler.
I came up with an idea. I’d create a meta-box on the right hand side of the page that was currently being edited, that would contain links to the other pages. This makes things much simpler for bulk editing pages. And I’m happy to say, that I’ve finally finished it and made it available to everyone for free! Right now, it’s just available through Github, but you can download a zip file of it from there as well if need be.
Big shout-out to Brett for all his help on this! Check out his blog here and find him on twitter @Socki.
Here’s the link! Let me know what you think. I’m totally open to contributions as well.
I’m trying to be a better developer. And somewhere in the vastness of the internet, someone told me I should be using Vagrant. So I did. It was cool and I liked it, but then I found out about VagrantPress and it got even easier. You just clone that repo, and start working. Pretty simple really.
But the other day, I was working on something that required an HTTPS connection, and I thought you should know how to do this with VagrantPress.
Firstly, startup your machine and login via SSH:
vagrant up
vagrant ssh
We’re going to need to do a few things with the SSL certificates, but I promise it’s painless.
sudo make-ssl-cert generate-default-snakeoil –force-overwrite
sudo a2enmod ssl
sudo a2ensite default-ssl.conf
sudo service apache2 reload
Credit for these handy commands goes to vtalbot here. Then, we need to edit a few files.
sudo nano /etc/apache2/sites-enabled/default-ssl.conf
Change the DocumentRoot /var/www/html to /vagrant/wordpress. Then we need to tell apache that it’s ok to share that directory so run this command:
sudo nano /etc/apache2/apache2.conf
and scroll waaaay down to all of the directives that look like
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
and create another one that looks like this
<Directory /vagrant/wordpress/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
After all of that, run
sudo service apache2 reload
Also, you’ll want to make sure your .htaccess file looks like this:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteBase /
RewriteRule ^index\.php$ – [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
The final step then is to navigate to your WordPress settings > General, and change both URLs to be HTTPS instead of HTTP.
And that should be all there is to it! You should now be able to access https://vagrantpress.dev/ and https://vagrantpress.dev/wp-admin/ problem free. Of course, your browser will probably tell you the connection isn’t trusted, but that’s just because you’re not using a signed SSL certificate.
The next step might be to go and change the URL in the WordPress settings, fiddle with your .htaccess if you want to force the SSL connection but for basic development purposes, this should get you where you need to go.