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.

Sorting category pages alphabetically

In a Facebook group someone wanted to know how to order the posts on a category page alphabetically. In this blog post want to show, how you can easily sort posts on a category (or any other) archive page.

Sorting post on all category pages

Even though I don’t know why someone would like to order posts by name (archives of a different post type would probably make more sense), this is the code to order them:

function aa_sort_all_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category() ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_all_archives' );

Anytime you want to alter the query, you would preferably use a callback function to the pre_get_posts hook. In this callback you would first check, if the main query is run. Then you check for what ever condition makes sense for your use case. If this condition is not met, exit the function. If all check succeeded, alter the query. So in line 7 we check, if we are on a category archive. If so, we change the order of posts to “ascending” (line 11) and the field by which we want to sort (line 12) to the post_title field. That’s all.

Order only posts in a specific category

As for some post types it might be unlikely, that you want to sort posts of all categories, you can also pass the category slug (or it’s name, ID) to the is_category() function. In this example, I’m using a dedicated category with the slug “alphabetical” and sort only posts on this archive page:

function aa_sort_alphabetical_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category( 'alphabetical' ) ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_alphabetical_archives' );

In the same way, you can have check for many other archive pages using different “Conditional Tags“. In the CODEX you will also find a page about “Alphabetizing Posts” but for category pages it tells you to use a secondary query. Please never do this! And also never show all posts! Just don’t do, what the page tells you 😉

Conclusion

Sorting posts (or other post types) on an archive page is best done by using the pre_get_posts hook. When you first use this hook, it might not work in the way you want it to work, but it’s worth learning how to use it correctly.

You can find the code of this blog post as a working plugin in a GIST where you can also download it as a ZIP and install it to your site.