Without docker:
wpcli wp plugin install [plugin-name] --force
With docker:
docker-compose exec -e HOME=/tmp --user 33:33 wpcli wp plugin install [plugin-name] --force
Without docker:
wpcli wp plugin install [plugin-name] --force
With docker:
docker-compose exec -e HOME=/tmp --user 33:33 wpcli wp plugin install [plugin-name] --force
When trying to update a plugin or similar action using the wordpress:cli
wpcli
docker image, for example using a command such as
docker-compose exec wpcli wp plugin update google-sitemap-generator
you see an error message such as
Warning: Failed to create directory. "/var/www/html/wp-content/upgrade/google-sitemap-generator.4.1.16" +--------------------------+-------------+-------------+--------+ | name | old_version | new_version | status | +--------------------------+-------------+-------------+--------+ | google-sitemap-generator | 4.1.13 | 4.1.16 | Error | +--------------------------+-------------+-------------+--------+ Error: No plugins updated (1 failed).
This error occurs because the wordpress
image (without :cli
!) is based on Debian and the wordpress:cli
image is based on Alpine Linux. Debian uses the UID 33
for the www-data
user whereas Alpine Linux uses 83
. So to fix the permission problem, you need to force the cli
image to use 33
:
This is documented on the wordpress
docker page.
docker-compose exec -e HOME=/tmp --user 33:33 wpcli wp plugin update google-sitemap-generator
Check the integrity of the WordPress core:
wp core verify-checksums
Check the integrity of all plugins:
wp plugin verify-checksums --all
docker-compose
variantdocker-compose exec wpcli wp core verify-checksums docker-compose exec wpcli wp plugin verify-checksums --all
wpcli wp db check
Or, with docker-compose
, if you have a wpcli
container:
docker-compose exec wpcli wp db check
wordpress.wp_commentmeta OK wordpress.wp_comments OK wordpress.wp_icl_content_status OK wordpress.wp_icl_core_status OK wordpress.wp_icl_flags OK wordpress.wp_icl_languages OK wordpress.wp_icl_languages_translations OK wordpress.wp_icl_locale_map OK wordpress.wp_icl_message_status OK wordpress.wp_icl_mo_files_domains OK wordpress.wp_icl_node OK wordpress.wp_icl_reminders OK wordpress.wp_icl_string_batches OK wordpress.wp_icl_string_packages OK wordpress.wp_icl_string_pages OK wordpress.wp_icl_string_positions OK wordpress.wp_icl_string_status OK wordpress.wp_icl_string_translations OK wordpress.wp_icl_string_urls OK wordpress.wp_icl_strings OK Success: Database checked.
While trying to create a user using wpcli
using a command such as
wp user create uli [email protected] --role=admin --user_pass=abc123abc
you see the following error message:
Error: Role doesn't exist: admin
Run
wp role list
to list all roles. By default, these roles are:
+---------------+---------------+ | name | role | +---------------+---------------+ | Administrator | administrator | | Editor | editor | | Author | author | | Contributor | contributor | | Subscriber | subscriber | | SEO Manager | wpseo_manager | | SEO Editor | wpseo_editor | +---------------+---------------+
You have to use the value from the second column in the wp user create
command.
For example, in order to create an admin
user, use --role=administrator
, e.g.:
wp user create uli [email protected] --role=administrator --user_pass=abc123abc
One one of my WordPress sites, the title of category pages was literally
Category <span>My category</span>
Searching through the theme source code didn’t yield a solution quickly, so I added this custom JS using a plugin:
jQuery(document).ready(function( $ ){ if(jQuery("h1.page-title").text().includes("Category: <span>")) { var input = jQuery("h1.page-title").text(); var output = /<span>(.*)<\/span>/g.exec(input)[1]; jQuery("h1.page-title").text(output); } });
This will just replace the title by the content of the span tag. You might need to adjust the CSS classes for your theme.
I recommend the Simple Custom JS and CSS plugin to add custom JS & CSS to your WordPress site.
It allows you to add multiple named custom scripts.
First, install the WordPress REST API Authentication
wordpress plugin, which you can find by searching for WordPress REST API Authentication
:
Then you need to open the plugin configuration page. Open Plugins
in the WordPress admin panel, locate the WordPress REST API Authentication
plugin and click Configure
Select Basic Authentication
:
Then click Next
on the top right:
and click Finish
on the next page:
Assuming you have a WordPress user admin
with password abc123
we can modify our code from How to get WordPress posts as JSON using Python & the WordPress REST API in order to query a non-public endpoint:
import requests import base64 # Compute basic authentication header auth_header = b"Basic " + base64.b64encode(b"admin:abc123") # posts is a list of JSON objects, each representing a post posts = requests.get("https://mydomain.com/wp-json/wp/v2/posts", params={"context": "edit"}, headers={"Authorization": auth_header}).json()
This script backups a WordPress installation (including data,base files & directories, excluding cache) to a bup
remote server running on 10.1.2.3
. You need to ensure passwordless access to that server.
It is based on automated extraction of database host, username & password, see How to grep for WordPress DB_NAME, DB_USER, DB_PASSWORD and DB_HOST in wp-config.php for more details.
#!/bin/bash export NAME=$(basename $(pwd)) export BUP_DIR=/var/bup/$NAME.bup export REMOTE_BUP_DIR=/bup-backups/$NAME.bup export REMOTE_SERVER=10.1.2.3 export BUP_REMOTE=$REMOTE_SERVER:$REMOTE_BUP_DIR # Init bup -d $BUP_DIR init -r $BUP_REMOTE # Save MariaDB dump (extract MariaDB config from wp-config.php) DB_NAME=$(grep -oP "define\(['\"]DB_NAME['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php) DB_USER=$(grep -oP "define\(['\"]DB_USER['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php) DB_PASSWORD=$(grep -oP "define\(['\"]DB_PASSWORD['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php) DB_HOST=$(grep -oP "define\(['\"]DB_HOST['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php) mysqldump -h$DB_HOST -u$DB_USER -p$DB_PASSWORD $DB_NAME | bup -d $BUP_DIR split -n $NAME-$DB_NAME.sql # Save wordpress directory bup -d $BUP_DIR index --exclude wp-content/cache --exclude wp-content/uploads/cache . && bup save -r $BUP_REMOTE -9 --strip-path $(pwd) -n $NAME . # OPTIONAL: Add par2 information # This is only recommended for backup on unreliable storage or for extremely critical backups # If you already have bitrot protection (like BTRFS with regular scrubbing), this might be overkill. # Uncomment this line to enable: # bup on $REMOTE_SERVER -d $REMOTE_BUP_DIR fsck -g # OPTIONAL: Cleanup old backups bup on $REMOTE_SERVER -d $REMOTE_BUP_DIR prune-older --keep-all-for 1m --keep-dailies-for 6m --keep-monthlies-for forever -9 --unsafe
In our previous post How to get WordPress posts as JSON using Python & the WordPress REST API we showed how to fetch a single page of 10
posts using the WordPress REST API in Python.
In this post, we’ll use the pagination in order to fetch a list of all the posts.
Firstly, we observe that once we query an invalid page such as ?page=1000000
, the returned JSON will be
{'code': 'rest_post_invalid_page_number', 'message': 'The page number requested is larger than the number of pages available.', 'data': {'status': 400}}
instead of the JSON array representing the list of posts.
Using this information, we can write a fetcher that fetches pages of 100
posts each until this error message is encountered:
from tqdm import tqdm import requests def page_numbers(): """Infinite generate of page numbers""" num = 1 while True: yield num num += 1 posts = [] for page in tqdm(page_numbers()): # Fetch the next [pagesize=10] posts posts_page = requests.get("https://mydomain.com/wp-json/wp/v2/posts", params={"page": page, "per_page": 100}).json() # Check for "last page" error code if isinstance(posts_page, dict) and posts_page["code"] == "rest_post_invalid_page_number": # Found last page break # No error code -> add posts posts += posts_page
You can use the requests
library to fetch the posts as JSON from /wp-json/wp/v2/posts
On the wordpress site, you typically don’t need to configure this – the JSON API is enabled by default and accessing publically available info can be done without authentication.
import requests # posts is a list of JSON objects, each representing a post posts = requests.get("https://mydomain.com/wp-json/wp/v2/posts").json()
This will, by default, fetch the most recent 10
posts. See TODO for more info on how to fetch all posts using pagination
Go to Plugins -> Install and search for WP Admin Category Search
. Install and activate it by clicking the button
I tested different local Google Fonts (GPDR) plugins and for some websites especially with Elementor/WPBakery etc, out of all plugins tested, only the OMGF (optimize my google fonts) plugin really worked in removing all fonts for GPDR compliance.
Therefore, I can recommend installing OMGF specifically, even though most other plugins like Self hosted Google Fonts will work for most websites.
You can installing it by opening the WordPress admin panel, clicking Plugins, clicking Install, and then entering OMGF in the search field.
You are running your WordPress instance using the official WordPress Apache image.
However, the WordPress Media page has a maximum upload size of 2 Megabytes
.
This setting is configured in the php.ini
used by the WordPress docker image internally. While it is possible to use a custom php.ini, it’s much easier to edit .htaccess
. Just edit .htaccess
in the wordpress
directory where wp-config.php
is located and append this after # END WordPress
to set the upload limit to 256 Megabytes:
php_value upload_max_filesize 256M php_value post_max_size 256M php_value max_execution_time 300 php_value max_input_time 300
The change should be effective immediately after reloading the page. Note that you still might need to configure your reverse proxy (if any) to allow larger uploads. My recommendation is to just try it out as is and if large uploads fail, it’s likely that your reverse proxy is at fault.
# BEGIN WordPress # Die Anweisungen (Zeilen) zwischen „BEGIN WordPress“ und „END WordPress“ sind # dynamisch generiert und sollten nur über WordPress-Filter geändert werden. # Alle Änderungen an den Anweisungen zwischen diesen Markierungen werden überschrieben. <IfModule mod_rewrite.c> RewriteEngine On RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteBase / RewriteRule ^index\.php$ - [L] RewriteRule ^en/wp-login.php /wp-login.php [QSA,L] RewriteRule ^de/wp-login.php /wp-login.php [QSA,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress php_value upload_max_filesize 256M php_value post_max_size 256M php_value max_execution_time 300 php_value max_input_time 300
Directly after any proxy_pass
line add
proxy_set_header X-Forwarded-Proto $scheme;
Typically X-Forwarded-Proto
is used together with X-Forwarded-Host
like this:
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme;
On your own blog or website, you often don’t care about the WordPress upload restriction and just want to be able to upload all filetypes, to just upload any filetype.
In order to do that, append this line to wp-config.php
:
define('ALLOW_UNFILTERED_UPLOADS', true);
and then, log out from WordPress and log back in again.
After that, you should be able to upload any file, no matter its file type.
This grep statement filters out the DB_NAME
, DB_USER
, DB_PASSWORD
and DB_HOST
contents from a wp-config.php
:
grep -oP "define\(['\"]DB_NAME['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php
For example, from the wp-config.php
line
define('DB_NAME', 'techoverflow');
it will extract techoverflow
.
Here are the statements for DB_NAME
, DB_USER
, DB_PASSWORD
and DB_HOST
:
DB_NAME=$(grep -oP "define\(['\"]DB_NAME['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php) DB_USER=$(grep -oP "define\(['\"]DB_USER['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php) DB_PASSWORD=$(grep -oP "define\(['\"]DB_PASSWORD['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php) DB_HOST=$(grep -oP "define\(['\"]DB_HOST['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php)
You can use this, for example, in automatic backup scripts without manually copying the password to the backup script
Your WordPress installation running on Ubuntu 20.04 shows you this error message:
OTGS Installer, responsible for receiving automated updates for WPML and Toolset, requires the following PHP component(s) in order to function:cURL Learn more: Minimum WPML requirements
You need to install the curl
package for your PHP version.
Typically you can use
sudo apt -y install php7.4-curl
Doing this should immediately fix the issue (just reload your WordPress dashboard). In case you’re using PHP 7.2 you need to sudo apt -y install php7.2-curl
instead.
In case the issue is still not fixed after doing this, try restarting your webserver (e.g. sudo service apache restart
or sudo service nginx restart
), try restarting PHP-FPM by using sudo service php7.4-fpm restart
or sudo service php7.2-fpm restart
or try rebooting your server. In case that doesn’t help either, you need to manually check which PHP instance the wordpress site is using and install the curl package for that PHP instance.
WordPress uses dashicons as its primary icon font. However, many themes do not require dashicons on the frontend and hence it’s only used in the admin frontend, i.e. when a user is logged in.
I made a simple plugin to make WordPress load the dashicons CSS only if the user is logged in. For non-logged-in users, the dashicons CSS is removed from the frontend (hence improving the loading speed of your website).
<?php /* Plugin Name: TechOverflow remove dashicons */ /** * Remove dashicons CSS from the page, only load if user is logged in */ function dashicons_admin_only() { if(!is_user_logged_in()) { global $wp_styles; wp_dequeue_style('dashicons'); // wp_deregister_style('dashicons') causes internal PHP errors in WordPress ! $wp_styles->registered['dashicons']->src = ''; } } add_action( 'wp_print_styles', 'dashicons_admin_only' );
Just create an new folder called techoverflow-no-dashicons
inside your wp-content/plugins
directory, and save the source code listed above as wp-content/plugins/techoverflow-no-dashicons/functions.php
inside. After that, you can modify the code to fit your specific needs.
Note that using this plugin might make your site appear differently if you are logged in. Hence it’s important to check your site when not logged in, e.g. using incognito mode.
Many WordPress plugins provide you with an option to configure whether scripts are loaded in the header or the footer of the page.
If you want to move a script to the footer for performance reasons, and the plugin doesn’t support it, one option is to just edit the wp_enqueue_script()
call in the plugin’s source code and set $in_footer = true
.
However, these change won’t survive plugin updates and hence are not recommended for security reasons.
A better option is to write a custom plugin that removes the <script>
tag from the head and moves it to the footer. The following file is the functions.php of my custom plugin that moves the <script> tag from the cookie-law-info plugin to the bottom of the page.
<?php /* Plugin Name: TechOverflow Cookie Law to footer */ function postpone_script($name) { global $wp_scripts; // Get attributes from original script $thesrc = $wp_scripts->registered[$name]->src; $theversion = $wp_scripts->registered[$name]->ver; // Remove script from the header wp_dequeue_script($name); wp_deregister_script($name); // Add script to the bottom wp_enqueue_script($name, $thesrc, false /* no deps */, $theversion, true /* bottom */); } /** * Move cookie law javascript to the bottom */ function postpone_cookie_law() { postpone_script('cookie-law-info'); } add_action( 'wp_print_scripts', 'postpone_cookie_law' );
Just create an new folder called techoverflow-cookie-law-footer
inside your wp-content/plugins
directory, and save the source code listed above as wp-content/plugins/techoverflow-cookie-law-footer/functions.php
inside. After that, you can modify the code to fit your specific needs.
Remember to check your page for issues (javascript errors, delayed rendering of some elements) that appear when you move the javascript to the footer, since some plugins may not be compatible with the script being placed in the footer. Also, you might need to call postpone_script()
multiple scripts with different $name
argument. Check the original plugin’s wp_enqueue_script()
calls for the correct $name
(first argument). In case it’s not obvious which wp_enqueue_script()
call relates to a specific script, you can also check the version in the src
of the original <script>
tag (e.g. 1.8.2
in ...?ver=1.8.2
) and search for that version in the plugin’s folder to find the matching wp_enqueue_script()
call.