WordCamp Europe 2022 – The community finally meets in Porto

It’s been three year. Three years after we had our last WordCamp Europe with attendees meeting in person, 2019 in Berlin. Back then I was the Local Lead and joined the next organizing team as one of three Global Leads. Then the pandemic changed the world and WordCamps became online events. While this year’s WCEU was not the first in person event, it was the biggest one, and it felt – almost – as in the past.

My first trip to Portugal

In 2020, I was supposed to visit Porto to see the venue we would use for WCEU 2020, but the pandemic hit us all, and we moved the WordCamp online and cancelled all local organizing work. Then this year in February there was another venue visit, this time for the new organizing team, but unfortunately I got COVID just some days before that. So on Monday, 30 May, I finally made it to Portugal for the first time.

Preparing the event

On Tuesday, I had the chance to get a first view of the amazing venue, the Super Bock Arena – Pavilhão Rosa Mota. The crew was already preparing the expo area and the screen for track 1:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

I had also the chance to meet some members of the organizing team for the first time in person and had lunch with some of them. As most things were already done or managed by the various teams, I had some time on Wednesday evening to visit the “Pirate Party” including a cruise on the river:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Kicking it off with the Contributor Day

Like many other WordCamps we also had a Contributor Day organized on Thursday.

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

The Contributor Day took place in front of the stage of track 1. We had around 800 contributors, many of them first time contributors. This was a new record for any WordCamp!

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

The first conference day

Friday was the first day with talks and workshops. The contributing tables were replaced by seating for the talks:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

I had the honor to open WordCamp Europe 2022 with our Local Team Jose Freitas and the three other Global Lesley Molecke, Moncho (José Ramón Padrón García) and Taeke Reijenga:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Unfortunately, I couldn’t see any talks on the first day. Only the first a half of Maja’s talk, before I had to rush out for some organizing work to be done.

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Just as in Berlin in 2019 we had more than just talks and workshops. We continued with the “Wellness Track”, offering yoga and meditation in the beautiful park around the venue, had the WP Café format where attendees could discuss topics, and we even had a live-streaming studio. Here we had interviews with different people from the event, which were live-streamed to the feed of track 1 in the breaks between talks. I also had the chance to be interviewed in one break:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

The second conference day

Saturday was the last day of WordCamp Europe. That day usually flies by pretty fast for organizers. At the end of the day we had around 2304 attendees in Porto and just before lunch many of them came together for a family photo:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

The last talk of WordCamp Europe 2022 was a Q&A session with Josepha and Matt:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

After this talk, it was time for the closing remarks. We had 91 organizers and 164 volunteers helping us making this all possible and having a great in-person event:

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

As always, we announced the city for the next WordCamp Europe at the end of the closing remarks and welcomed the new team …

WordCamp Europe 2022 Athens

Next year, the WordPress community will meet in Athens, Greece! Another country I have not yet visited.

Click here to display content from Twitter.
Learn more in Twitter’s privacy policy.

Time to “hang my hat”

For me, this was the last WordCamp Europe as an organizer. I’ve joined the team back in 2017 and only took a break last year for the second online edition. Following the “tradition” of being a Local Lead and then a Global Lead, I will probably be speaking next year, maybe sharing the stage with my fellow Global Leads from 2020 and 2022. As much as I loved organizing all these events, it’s time to make room for new community members to step up and join the team. I’m very much looking forward to what the WCEU 2023 crew will surprise us with!

Make changes to PHP ini values without losing them on an update

If you have ever set up a server yourself, you probably realized, that the default values for PHP don’t really work for modern websites. The upload_max_filesize is set to only 2 MB, which means that you cannot upload anything into the media library of WordPress that is larger than 2 MB. This value is usually one you want to change for any site. But how can you change the value and not risking your modifications to be reverted, one you upgrade PHP or your server.

Changing the global PHP ini values

This blog is running on Ubuntu 22.04 with PHP-FPM running alongside the nginx web server. If I want to make changes to any PHP ini variable, I would make those changes to the file /etc/php/8.1/fpm/php.ini on a global level. As you can see in the file path, you would have to make these changes to the php.ini file of each installed PHP version. You would also need to do the change for any “server API”, so if you also use CGI/FastCGI for some site, you have to make the same changes to the file /etc/php/8.1/fpm/php.ini as well.

The advantage of this change is that any website hosted on this server will use these settings, so you can set good defaults for any site. But as you make changes to global configuration files, you will run into issues once you upgrade your server. When the package manager wants to upgrade the configuration file, it detects changes and asks you to either use the new file, use your modified file or to examine the changes and resolve them yourself. Depending on how many changes you have made, this might be difficult to make.

In order to modify PHP ini values globally, I have added a new file called /etc/php/conf.d/90-custom.ini with only the variables I want to change and them symlink them into the different configuration paths:

$ tree /etc/php/   
/etc/php/
|-- 7.3
|   |-- cgi
|   |   |-- conf.d
|   |   |   |-- 10-mysqlnd.ini -> /etc/php/7.3/mods-available/mysqlnd.ini
|   |   |   |-- ...
|   |   |   `-- 90-custom.ini -> /etc/php/conf.d/90-custom.ini
|   |   `-- php.ini
|   |-- cli
|   |   |-- conf.d
|   |   |   |-- 10-mysqlnd.ini -> /etc/php/7.3/mods-available/mysqlnd.ini
|   |   |   |-- ...
|   |   |   `-- 90-custom.ini -> /etc/php/conf.d/90-custom.ini
|   |   `-- php.ini
|   |-- fpm
|   |   |-- conf.d
|   |   |   |-- 10-mysqlnd.ini -> /etc/php/7.3/mods-available/mysqlnd.ini
|   |   |   |-- ...
|   |   |   `-- 90-custom.ini -> /etc/php/conf.d/90-custom.ini
|   |   |-- php-fpm.conf
|   |   |-- php.ini
|   |   `-- ...
|-- ...
|-- 8.1
|   |-- cgi
|   |   |-- conf.d
|   |   |   |-- 10-mysqlnd.ini -> /etc/php/8.1/mods-available/mysqlnd.ini
|   |   |   |-- ...
|   |   |   `-- 90-custom.ini -> /etc/php/conf.d/90-custom.ini
|   |   `-- php.ini
|   |-- cli
|   |   |-- conf.d
|   |   |   |-- 10-mysqlnd.ini -> /etc/php/8.1/mods-available/mysqlnd.ini
|   |   |   |-- ...
|   |   |   `-- 90-custom.ini -> /etc/php/conf.d/90-custom.ini
|   |   `-- php.ini
|   |-- fpm
|   |   |-- conf.d
|   |   |   |-- 10-mysqlnd.ini -> /etc/php/8.1/mods-available/mysqlnd.ini
|   |   |   |-- ...
|   |   |   `-- 90-custom.ini -> /etc/php/conf.d/90-custom.ini
|   |   |-- php-fpm.conf
|   |   |-- php.ini
|   |   `-- ...
|-- ...
|-- conf.d
|   `-- 90-custom.ini

As the new 90-custom.ini is not a file that package manager would add/update, any changes made in this file would be safe from upgrades.

Changing PHP ini values per site

Sometimes you want or need to change values for a specific site. Then using the global file would not work. If you are using the Apache web server, you might be able to change values using the Apache configuration files or even using the .htaccess file. In this file, you would use something like php_value upload_max_filesize 64M to increase the upload limit. This, although, will only work if you use PHP as an Apache module.

If you use Apache with PHP-FPM or different web server – like I am using nginx – you cannot use the .htaccess file to make any changes to the PHP ini values.

Using the “user_ini” file for per site changes

Fortunately, you have another way to change PHP ini values. This can be done with a “user_ini” file, you usually store in the document root of the site. First, you have to find out what the filename for this file should be. You can find out with the phpinfo() function or by running php -i and searching for the user_ini.filename value. You then create this file and add any changes writing it in the same way as in the php.ini files, so like upload_max_filesize = 64M to change the upload limit.

When you make changes to this file and check if they work, they probably don’t! This does not mean that, that you’ve made a mistake. It probably means that the file was not read by the PHP process. This is because the file is cached. You can find out for how long by looking at the user_ini.cache_ttl value. This is the time, in seconds, the file is cached. If the value is 300, you have to wait up to 5 minutes for changes to be used for the site. If you need the changes to be used immediately, you have to restart the PHP process (like PHP-FPM) or the web server (when you use Apache mit mod_php).

Conclusion

There are multiple ways to make changes to PHP ini variables. When you have to make changes, always make sure to use a method that would not break upgrades (or the changes getting lost with the upgrade). For my own server, I prefer to use the first approach with a global custom ini file. The user_ini file has the advantage that these value could easily be transferred to a new server when migrating a site, so they might be better for PHP systems that need specific values.

Fixing the Matomo opt-out iframe when using secure headers

Last year, I wrote a blog post about how to secure your website with server headers. When changing the security headers, there is always the chance that some things stop working, as they need “more unsecure” settings. One of these example is the Matomo opt-out iframe and in this blog post I want to show you how to solve it.

The opt-out gets blocked by the browser

If you use Matomo to track your visitors and collect statistics on how they use your site, it’s best to always use the most privacy-friendly settings. You should run the tracking without cookies and also enable the “Do Not Track” option. But even with these settings, you should allow your visitors to opt-out from tracking. To do this, you can create an iframe in the privacy settings of Matomo. This iframe is usually added to your privacy page. If you do this with secure headers set for Matomo, you might get the following error message in browsers:

Refused to display ‘https://matomo.example.com/’ in a frame because it set ‘X-Frame-Options’ to ‘sameorigin’.

This happens when the security headers are too tight not allowing a website with a different domain to embed them.

Allow external domains

One thing you could do now is changing the value of the X-Frame-Options to something more insecure. But we don’t really want that. Instead, you can explicitly allow some domains to embed the iframe. This can be done using the Content-Security-Policy header:

Apache

Header set Content-Security-Policy "frame-ancestors 'self' https://example.com/ https://www.example.com/"

nginx

add_header Content-Security-Policy "frame-ancestors 'self' https://example.com/ https://www.example.com/"

You might already have a Content-Security-Policy set in your server headers. In this case, you can also add these options to the existing header. If you don’t know how to combine different options, the generator from Report UI may be you with this.

Conclusion

Securing a website is very important. But sometimes a higher level of security can interfere with other things. Always make sure to test all things after changing something on the server headers. If visitors are unable to opt-out from tracking, you might have solved a potential security issue, but now you have a privacy issue.

Allow co-editing of content by multiple users

WordPress comes with different roles, and it’s usually best not to give everyone the “Administrator” role. The next best role would be the “Editor” role, which enables a user to edit all contents, their own one and the content from other users. But in some cases, this role is also too permissive.

Allow a user to edit their content only

When you want to limit a user to edit only the posts and pages they have created, assign them to the “Author” role. With this role, they can see content of user users, but they cannot edit them. This is perfect, if you have a page where you want to invite many external users to edit “their pages”, maybe a page with a profile of their own company. They can either create this profile themselves, or you would create it and assign them as authors to that profile page.

But sooner or later you have the request from one of these users, that they want to have another person being able to edit this page as well. As WordPress can only have one author assigned to a page, this is not easily possible and as you probably don’t want to ask them to share the login password, we need a better solution for this.

The “Co-Authors Plus” plugin to the rescue

Through a tweet mentioning some plugins someone was regularly using, I found the plugin Co-Authors Plus, which has been around for many years, which tackles this need. It allows you to add more than one author to a post or page. The first author in the list will be shown at the single view of the post, but the plugin also offers functions to show the other authors as well (be adding those functions to a child-theme template).

Those different authors can even have different roles. But any user, who is either the primary/first author or an additional author, can edit the page, as long as their role would allow them to do so.

As WordPress usually stores the author of a post or page in a single column in the wp_posts table I was curious to see, how they solved it, and found out, that they use a custom post type to assign the authors to a post.

Conclusion

While the basic roles of WordPress are usually enough for various requirements, the issue of co-editing was always something, I was wishing for a core solution. But with the addition of this plugin (which still gets updates and even integrates nicely into the block editor and “Quick Edit”), this can also be established quite easily.

Overcome the “ERROR 1153” when importing large databases

Migrating a WordPress site is something I do a lot on a weekly basis. Many migrations happen between local environments or production and staging. The source and target systems don’t necessarily have the same settings. From time to time you have to migrate a site with a large database. In these cases, you might run into this error:

ERROR 1153 (08S01) at line 56: Got a packet bigger than 'max_allowed_packet' bytes

This can happen when a single INSERT statement on the import is too large. In this blog post, I want to give you tips on how to import the database.

Create a new backup with smaller imports

When you export your databases, you probably use either the mysqldump command, the wp db export or a database management tool such as Adminer or phpMyAdmin. In the default settings, they will create INSERT statements that would not only import a single line into a table, but multiple of them. This is usually a good strategy, as it speeds up the import. But if one of those statements become too long, you will run into the error.

To solve it, you can try to create an export of the database, that only has one single row in any INSERT statement. This will make the import run longer, but it’s more likely it succeeds.

To create an export like this, you can use the WP-CLI with some additional mysqldump arguments:

wp db export --extended-insert=FALSE --complete-insert=TRUE

This should create such an export file. Now you should hopefully be able to import that database dump.

Increase the “max_allowed_packet” value

If you are still unable to import the database dump file, you probably have to increase the value of “max_allowed_packet”. The default value can usually only be changed by the server administrator. But you can often increase the value temporarily. For this, you connect to the MySQL server, increase the value and import the file. I will use the wp db cli command from the WP-CLI. This will connect me to the correct database of my WordPress installation. Now I can run the commands:

SET GLOBAL max_allowed_packet=1*1024*1024*1024;
SOURCE db_dump_filename.sql;

The first command will set the variable globally to 1 GB. Unlike in the MySQL configuration file, you can not use a value of “1G” in this statement, but you have to use a number in bytes. But as you can also use a calculation while setting the value, in the above notation it’s easier to see, what the value in bytes will be.

Conclusion

Importing large databases can be an issue. But with these tips you can hopefully import it yourself. If not, ask a server administrator to help you out. They probably have tools to do the import for you. And if you yourself write data into the database, please make sure rows don’t get too large.

How to remove the “update lock” after a broken WordPress update

Updating WordPress and it’s plugins and themes is usually straightforward. You navigate to the “Dashboard | Updates” and update the components. If an error happens while updating WordPress Core or plugins/themes, WordPress can usually handle this and roll back, but sometimes things go wrong.

Failed updates break the site

If the update fails, your site might end up being broken. In this case, you will probably get the following message:

Briefly unavailable for scheduled maintenance. Check back in a minute.

When WordPress performs updates for core (or multiple plugins), it will enable a maintenance mode and inserts a lock into the database.

Repairing the site

The two locks will be automatically removed after 15 minutes. As it will not only break the backend, but also the frontend of your site, you probably don’t want to wait 15 minutes.

Deactivating the maintenance mode

The first thing you can do is searching for the file .maintenance file in the root folder of your WordPress installation. This file contains a single line of PHP code with the UNIX timestamp on when the installation process of the update started:

<?php $upgrading = 1648994440; ?>

If you delete this file, the message will disappear and WordPress will show the frontend and backend of your site again.

Resuming the previous update

Even after removing the .maintenance file, you can’t continue updating WordPress core. If you try to update it right away, you will probably see the following message:

Another update is currently in progress.

This is caused by the core_updater.lock option WordPress sets when starting the core update process:

$ wp option get core_updater.lock
1648994423

As you can see, it has a slightly earlier time (it’s set before the ZIP is being downloaded and extracted). When you are sure that the previous core update process really stopped, you can remove this option either using a database management tool or with the WP-CLI:

$ wp option delete core_updater.lock
Success: Deleted 'core_updater.lock' option.

Now you should be able to restart the update process, this time hopefully without an error causing your site to break again.

Conclusion

While WordPress updates usually don’t cause issues and run quite smoothly, an error can always happen. In these cases, you should know how to quickly get your site working again and not having it broken for 15 minutes.

Repair a broken Git Repository file system

Some weeks ago, I had quite an unusual issue with Git. In a repository I hadn’t used for quite a while, I simply wanted to see, if I had any changes, so I ran a git status on it with the following result:

$ git status
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
fatal: loose object 6eeab7d4770c705a0491cafbc95830af69d5c6a2 (stored in .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2) is corrupt

I’ve never experienced that before. Fortunately, Git offers some commands to check a Git repository, so I did a file system check:

$ git fsck --full
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
error: unable to mmap .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2: No such file or directory
error: 6eeab7d4770c705a0491cafbc95830af69d5c6a2: object corrupt or missing: .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2
Checking object directories: 100% (256/256), done.
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
fatal: loose object 6eeab7d4770c705a0491cafbc95830af69d5c6a2 (stored in .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2) is corrupt

This gave me a bit more verbose information that one object was corrupt, but still no help in how to solve it, which Git usually gives you when using a command.

The Solution

With this new information, I was able to find a solution on Stack Overflow. I just had to delete the corrupt/empty file. In my case, it was really just this one file. In other repositories, there might be multiple files. To quickly delete them all, we can search the Git folder for all “empty” files and delete them:

$ find .git/objects/ -type f -empty | xargs rm

After this command, all corrupt files are missing from the repository. We can get them back by fetching the data from the remote:

$ git fetch -p
remote: Enumerating objects: 228, done.
remote: Counting objects: 100% (228/228), done.
remote: Compressing objects: 100% (91/91), done.
remote: Total 210 (delta 121), reused 188 (delta 99), pack-reused 0
Receiving objects: 100% (210/210), 90.23 KiB | 2.65 MiB/s, done.
Resolving deltas: 100% (121/121), completed with 11 local objects.

Finally, we make another file system check to see if all errors are gone:

$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (221/221), done.
dangling blob c5446110414804bbba2a5316a3e996ff37666bb9
dangling blob 45dd1301284105bcfc7e183bc805b65bf1465f47
dangling blob 70376fcbe5060d0db11490249bed5b553c0d04cc

Conclusion

Usually, Git gives us quite useful error messages, when we do something wrong. In this case I had to research a bit but fortunately was not the first one to encounter this issue.

Our fix only worked without any losses, because we were able to fetch the deleted corrupt/empty objects from a remote. This is why I always advise people to always have their code in a remote repository as well, and commit and push often.

Volunteering at a WordCamp

Today’s blog post is a personal one and a call for people from the community. I want to talk about my experiences volunteering at a WordCamp.

My very first volunteering experience came quite late. I’ve attended my first WordCamp in 2010, organized the first event back in 2012 and spoke at many WordCamps since. Those types of contributions are also very valuable. But they are quite different from volunteering.

WordCamp Europe 2016 in Vienna

I don’t know exactly when I first thought about bringing WordCamp Europe to Germany, but back in 2015 I’ve joined a group of community members to talk with the current organizing team on how we could make it happen. When trying to organize an event this size, it’s usually best to have some experience in how it’s organized. So to me, it made a lot of sense to volunteer at the next event.

My first shift was as a “Foyer Guard” at the speaker’s green room and child care, which was quite interesting. I’ve only seen child care being offered at WordCamp London, but really felt that this was very important for some attendees travelling to Vienna with children.

My second shifts at the first day was at the registration welcoming new attendees. At this shift I was also not alone but joined by other volunteers. This made the shift much more fun, not only because I was able to work with other volunteers, but also because I was meeting so many people, some of which I have known for some year.

On the second day, my position was at the “Lunch Attendee Orientation”, so I basically told people where to get lunch – in case they haven’t had found it already at day one. My last shift was as a “Door Guard” for the largest stage. I made sure that attendees coming late would not make too much noise entering the room.

WordCamp London 2017, 2018 and 2019

My next volunteering change came in 2017 at WordCamp London. Only about a month before the event, the organizing team was asking for help, as they had some drop-offs amongst the volunteers. I already bought a ticket, but still volunteered as help was needed. I was also able to join an amazing “warm up” speakers, organizers and volunteers. It was a bar with “table tennis”! Really a lot of fun!

Just one year later, I volunteered again at WordCamp London. It’s one of my favourite WordCamps. At this event there was a kids workshop hosted by the WordPress co-founder Mike Little. I was amazed what some school kids were doing in these few hours the workshop lasted.

I volunteered a third time at WordCamp London in 2019. Here my first shift was in the storage room / cloakroom. I’ve helped sponsors store their boxes after setting up their booths and prepared some “swag bags” for attendees. After my shift, I’ve enjoyed the WordCamp and saw some session. Later, the WordCamp lead organizer found me to apologize. She said that she hasn’t realized, while planing the volunteers shifts, that she was putting the current Local Team Lead of WordCamp Europe into the cloakroom. But I assured her that there was no need to apologize. I was just a volunteer like any other and my help was needed in the cloakroom, so I worked my shift there.

Conclusion

Everyone who is helping out at a WordCamp as a volunteer is much needed and appreciated, no matter how small and insignificant your work might seem. Volunteering is also the first step of becoming a future organizer. There is no rule that you had to be a volunteer before joining an organizing team, but it helps a lot. Not only do you get a first idea of how organizing an event works, you will also know what it means to be a volunteer at an event, who might not even know anything about WordPress, and it’s community. So whenever there is a WordCamp near you, I would highly encourage you to look out for the Call for Volunteers to see if you can help. It will give you a very new perspective of the event.

And speaking of WordCamps who need more volunteers: WordCamp Europe is happening in June and you can still apply to volunteer until 31 March 2022. If this very blog post made you apply, please make sure to find me at “The Social” (this is what we call the “warm up” party at WordCamp Europe) and share some stories with me.

How to enqueue scripts and styles effectively

When using custom JavaScript and CSS files, you usually use the wp_enqueue_script() and wp_enqueue_style() functions. If not, please start doing so. Using them is really easy, but when it comes to effective usage, there are some little things to keep in mind.

Simple usage

The easiest way in using them is by only defining a “handle” and the URL to the file you want to enqueue:

wp_enqueue_script(
	'chart-js',
	'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js'
);

This example would load a JavaScript files hosted on an external server into your page. This can be all fine, but for many things you would rather want to have them locally, so you avoid issues when the CDN is down or with privacy.

Using a local file – bad example

To load a file in a plugin or theme, it would be enough to reference it with a relative path like this:

wp_enqueue_script(
	'chart-js',
	'/wp-content/plugins/plugin-name/assets/chart.min.js'
);

This would enqueue it in the source code in the following way:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=5.9'></script>

While this would work, there are some issues with this simple way.

1. Always use dynamic paths

In the example above, we use a relative path to the wp-content/plugins folder. This might seem OK for most of you, but as the wp-content folder can have a different name (some security plugins do that – which is usually not a great idea), you should always use helper functions to get the relative path to that folder. If you enqueue a file in a plugin or theme, there are different functions you can use. These are the ones, you would usually use:

2. Always provide a version to the file

As you can see in the example above, WordPress will add a version number. If you don’t define one yourself, it would append the current version number of WordPress, which today would be 5.9, to the end of the URL. This version number is meant to help you with caching. As your files will probably not (only) change when WordPress gets updated, you should use a different version string. This could be one of the following:

  • A static version number matching the version of the plugin
  • A static modification date of the plugin
  • A static modification date of the file that’s enqueued
  • A dynamic modification date of the file that’s enqueued

In the past, I have used the first or third option. But then you have to update all these string (for all files) manually and it’s easy to forget one, which will result in the browser loading old files from it’s cache. Nowadays I usually use the latest apporach. This also has the benefit that every time you save any file while still developing it, you don’t have to take care of the cache, your browser will always load the latest file. An example of this approach – combined with dynamic paths – could look like this:

wp_enqueue_script(
	'chart-js',
	plugins_url( 'assets/chart.min.js', __FILE__ ),
	array(),
	filemtime( plugin_dir_path( __FILE__ ) . 'assets/chart.min.js' )
);

This would then result in the file being included like this:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=1644792351' id='chart-js-js'></script>

As you can see, we now have a UNIX timestamp appended to the end of the URL. As this changes with every safe of the file, the browser will always load the most recent file version.

Conclusion

This blog post was covering a very basic concept, but I have seen so many plugins and themes out there still enqueuing files not in the best way. With these little tricks, you can avoid a lot of stress finding issues with your code, when in the end, it was just an old file being loaded from your browser’s cache.

Dynamic form specific hooks for GravityForms

I really like using GravityForms to create highly dynamic forms. The form builder offer a wide range of possibilities. But sometimes you need to use some code to hook into parts of the form processing. In this blog post I want to show you how to make this work even across multiple installations of a form.

Using a hook for all instances

Let’s take the gform_pre_submission as an example. If you want to hook into it, you would do as with any other hook in WordPress:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission', 'my_pre_submission' );

This code snippet would update every form field with ID 1 on every form. This is probably not what you want to do.

Using a hook for only one form

When changing a value, you usually do that for a specific form. This can be done by adding the form ID to the end of the hook:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_1', 'my_pre_submission' );

Now, the value only get updated when the form with ID 1 is submitted. That could be a good solution, until you export/import your forms.

Handling different form IDs

Let’s say you have created a form for one project, and now you want to use it in another project. You can imply use the export/import of forms to copy over all settings of a form (without the entries). But if your other project already has forms, then your imported form might have a different ID on this new project. The same could happen, if you copy the form from a development environment to a live site, or if you are not the only one creating new forms.

In this case, you would have to find out your new form ID and change the values for the hooks. But what if you have these hooks in version control, and you can’t change it to a new static value?

Using a constant for the form ID

In a project with this issue, I’ve decided to use constants to store the form IDs. All hooks now look something like this:

function my_pre_submission_contact( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_CONTACT_FORM_ID, 'my_pre_submission_contact' );

function my_pre_submission_form_event( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_EVENT_FORM_ID, 'my_pre_submission_form_event' );

This is not the actual code, but I hope you get the idea. In using a constant per form (using the form name, not the ID), you can dynamically set the ID of the given form depending on the system the form is uses.

Set constant defaults in the plugin (or theme)

I usually store such code in a plugin. In the main file of this plugin, I would set the values for the constants on the system I’ve first used/created these forms:

if ( ! defined( 'PREFIX_CONTACT_FORM_ID' ) ) {
	define( 'PREFIX_CONTACT_FORM_ID', 1 );
}
if ( ! defined( 'PREFIX_EVENT_FORM_ID' ) ) {
	define( 'PREFIX_EVENT_FORM_ID', 2 );
}

By adding the ! defined() check, this would only set the constant, if it was not set earlier in another file. So on the second project, where the IDs are different, you would just define them in your wp-config.php for example:

define( 'PREFIX_CONTACT_FORM_ID', 4 );
define( 'PREFIX_EVENT_FORM_ID', 5 );

Conclusion

While GravityForm really offer a lot of hooks and using them is really flexible, it can be an issue when using a static form ID calling a hook. With a constant per dynamic ID value, you can use the same form and the same custom hooks in different projects.

The same would apply to all the other hooks from GravityForms where you can append an ID to only target a specific form, field, etc.