• Skip to main content
  • Skip to primary sidebar

Ani's Webdev Blog

A learning diary of website development

Blog

WordPress + Gumlet + S3: Resizing images from server side and upload to S3

1. Task

In this post, we will use Gumlet PHP Image Resize library to compress images uploaded to WordPress and then move them to Amazon S3 in one work session.

a. Pros and Cons

  • Pros: By using a PHP library, we don’t need to pay for or install extra plugins
  • Cons: Have to code, follow the maintenance of the Gumlet library and if other plugins are using also Gumlet there might be conflicts

b. Pre-requisites

For user who wants to make this workflow, before continuing please check that:

  • You can install libraries with composer,
  • You have access and rights to modify your plugin / theme where you want to perform this compress,
  • You have credentials for S3 buckets where to store the images

c. Scope

This post consider two situations of image upload:

  • When user upload image via a front-end form, and image will be submitted via $_FILES,
  • When user upload with Media handler from WordPress function.

2. How to resize images in WordPress with Gumlet

a. Install Gumlet

First of all, we need to install Gumlet PHP Image Resize library to our theme/plugin. From now on, I’ll refer to the theme / plugin where we want to make the resize as “project folder”.

Instructions in GitHub page (https://github.com/gumlet/php-image-resize) is quite straightforward. What we need to do, is to add the image resize library to our composer.json. If the project folder does not have this file yet, just create it.

The file will contain in the require block:

"gumlet/php-image-resize": "2.0.*"

And if it is a total new composer.json file, it will look like:

{
    "require": {
      "gumlet/php-image-resize": "2.0.1",
      "ext-gd": "*"
    }
}

Currently I am using version 2.0.1 and it works well with WordPress 6.0.3.

After adding the line, go to the Terminal / Command line of the project folder and install it

composer install

This process will create a new vendor folder in the project. If you already have a vendor folder, this installation will add in to the existing folders inside vendor a new folder called gumlet.

adding gumlet with composer

b. Use Gumlet to resize image when user uploads via Media

If user uses Media handler to upload images, even in front end or in admin area, we can hook into a WordPress filter and start resizing the images when it gets into the system.

In the beginning of the function file (depends on your use case it can be functions.php, a separate function file or plugin classes), state the use of Gumlet then hook it to wp_handle_upload_prefilter filter:

use \Gumlet\ImageResize;
use \Gumlet\ImageResizeException;
require __DIR__ . '/../vendor/autoload.php';
// Using Gumlet to resize all images to width 1280
add_filter( 'wp_handle_upload_prefilter', 'ak_resize_image' );
function ak_resize_image($file) {
	$images = array('image/png', 'image/jpg', 'image/jpeg');
        if(in_array($file['type'], $images)) {
		try {
			$resize_image = new ImageResize($file['tmp_name']);
			$resize_image->resizeToWidth(1280);
			$resize_image->save($file['tmp_name']);
		} catch( ImageResizeException $e ) {
			// Something you want to do with the error, 
                        // for example signaling Sentry
		}
        }
	return $file;
}
/** 
 * WordPress already compresses images,
 * but we can change the value to be a bit lower,
 * to have lighter file
 */
add_filter( 'jpeg_quality', 'ak_change_compression_quality');
function ak_change_compression_quality() {
     return 80;
}

Uploading to S3

For the media upload to directly upload to S3, you can use a plugin that make the route from Media to S3 more convenient, for example S3 Uploads by humanmade or WP Offload Media by Delicious Brain.

Once the route to S3 is set, all images that uploaded via Media will fly straight to S3, and the images that were resized by Gumlet also follows this way.

If you would like to upload an image to a separate folder of S3 programmatically, read on to the c section.

c. Resize images uploaded via front end form and move to a separate folder in S3

Let’s skip the front end form part and jump straight to server handler. Here, supposes that we have the image via $_FILES, let’s continue from here.

Like the above code, we need to start by stating the use of Gumlet in the beginning of the file. The function below lets us resize the avatar uploaded via front end file to 300 by the shorter side, then upload to an S3 folder name “avatar”. In my use case, I return the final URL of the S3 destination for the file or a WP_Error. You can tweak it based on your needs.

use \Gumlet\ImageResize;
use \Gumlet\ImageResizeException;
require __DIR__ . '/../vendor/autoload.php';
/**
  * Resize image to be 300 in width with Gumlet
  * Upload image to S3, folder 'avatar'
 */
function ak_upload_user_avatar($image)
    {
        $upload_dir = wp_upload_dir();
        $image_type = $image_info[1];
        // Do a quick validation for image type and size
        // Avatar should be small
        if ($image['type']) {
            $valid = ['image/jpeg', 'image/jpg', 'image/png'];
            if ( ! in_array($image['type'], $valid) || $image['size'] > 512000) {
                return new WP_Error('upload-user-avatar', 'Wrong image format or file size exceeds 500kB.');
            }
        }
        // Resize and compressing image using Gumlet php-image-resize
        try {
            $resize_avatar = new ImageResize($image['tmp_name']);
            $resize_avatar->resizeToShortSide(300);
            $resize_avatar->save($image['tmp_name']);
        } catch (ImageResizeException $e) {
            return new WP_Error('upload-user-avatar', 'Error when resizing.');
        }
        $file_path = $upload_dir['basedir'] . '/avatar/' . $image['name'];
        $success = true;
        if ( ! $success) {
            return new WP_Error('upload-user-avatar', 'Can not upload avatar.');
        }
        return { S3_BASE_URL } . 'avatar/' . $image['name'];
}

When using the function, we can call:

$image_url = ak_upload_user_avatar($_FILES['avatar']);

For different projects, the location and how those functions were placed will be different. Hopefully this post can help to visualize the route of resizing images with Gumlet.

[WordPress] Using Child Themes

Why Do We Need to Use a Child Theme in WordPress?

Earlier when WordPress is mainly for the purpose of blogging, a theme decides the style of a website, or how the website looks like. Nowadays WordPress are getting more complicated to serve many purposes: business websites, portfolios, listing directories, and so on. The website structures are not as simple as before.

For example, with Superlist theme, you can have a listing directory website with useful features such as maps, filtering information, claiming a property, etc. Or, with Newspaper theme, you will have stunning websites that are optimized for speed and provide plenty of ad spaces to display advertisements.

That is to say, nowadays a theme is also an engine to run a website of a specific purpose. Theme makers update their themes frequently not only to fit in with the WordPress updates, but also to release new features, fix old bugs, and so on.

When you update a theme from the WordPress dashboard, all of the old theme code will be replaced with the new one. That means, if you edit a line of code in the theme, after next update, it will not be there anymore.

That’s where a child theme come into play. A child theme purpose is simple: to keep all your customization not to be overwritten by updates of the parent theme. It is also very simple to install a child theme, please refer to this post for a step-by-step tutorial.

How to Customize WordPress with a Child Theme

After creating a child theme, we will have at least two files in that child theme folder:

  • styles.css: to overwrite CSS in the parent theme
  • functions.php: to write custom functions

To make further changes, we will need to copy some files from the parent theme to this child theme folder and start modifying here.

Information

In order to use child theme, you will need to have both the parent theme and child theme in the wp-content/themes/ folder. Activate the child theme (after that, you may need to re-save the Menu).

WordPress Theme Anatomy

Before making any modifications, we would need to know where the files are located. Inside a theme folder, you will find (or should create):

  • header.php: The header of a page, usually have meta tags, title, etc.
  • footer.php: The footer of a page, usually have footer widgets, or a credit line “Powered by WordPress | Theme name” for a free theme version
  • single.php: The template for a single post
  • home.php: The template for blog page
  • archive.php: The template for categories / tags archive pages
  • front-page.php: Not included by default, but you can create this as the template for homepage
  • sidebar.php: Contains sidebar info + sidebar widgets

So if you want to modify a page, you need to:

  • Find the correct template
  • Make changes
  • Upload to the child theme folder so that it overwrites content in the parent theme.

Example 1. Display modified date from a WordPress post instead of published date

Displaying the modified date is a good way to inform the visitors that the blog is not out of date. And also, it is good for SEO CTR because users tend to visit the post that is not too old. Theoretically, the date is a meta information that will be found in single.php page templates. Some themes even make it into a function.

The basic principal: You would need to find the right place and change the get_the_date() (or the_date()) function with get_the_modified_date() (or the_modified_date())

Because each theme is different, it’s hard to say where it is. But, starting from single.php or content-single.php, you will find the clue.

For example, if it is like this, that mean the meta has been embedded inside a function, you should find this function somewhere:

A piece of content from content-single.phpA piece of content from content-single.php

Here you can find some of the examples:

1. Newspaper theme

  • wp-content/plugins/td-composer/legacy/common/wp_booster/td_module_single_base.php
  • wp-content/plugins/td-composer/legacy/common/wp_booster/td_module.php

2. Accelerate theme

The meta information in Accelerate theme is written into a function, so let’s just overwrite it by adding the same function in our functions.php in the child theme.

This piece of code is from Accelerate 1.4.3.

You can copy and paste the function (accelerate_entry_meta()) into your child theme’s functions.php, changing this line:

printf( '<span class='posted-on'><a href='%1$s' title='%2$s' rel='bookmark'/><i class='fa fa-calendar-o'><i> %3$s</a></span>', esc_url( get_permalink() ), esc_attr( get_the_time() ), $time_string);

Into this:

printf( '<span class='posted-on'><a href='%1$s' title='%2$s' rel='bookmark'/><i class='fa fa-calendar-o'><i> Updated on: %3$s </a></span>', esc_url( get_permalink() ), esc_attr( get_the_modified_time() ), $time_string);

And replace this:

$time_string = sprintf( $time_string, esc_attr( get_the_date( 'c' ) ), esc_html( get_the_date() ), esc_attr( get_the_modified_date( 'c' ) ), esc_html( get_the_modified_date() ));

With this:

$time_string = sprintf( $time_string, esc_attr( get_the_modified_date( 'c' ) ), esc_html( get_the_modified_date() ), esc_attr( get_the_date( 'c' ) ), esc_html( get_the_date() ));

And you can see the change on front end, from Published date into Updated on:

3. UnderStrap theme

The meta information in Understrap theme is written into a function, so let’s just overwrite it by adding the same function in our functions.php in the child theme.

/**
 * Override parent theme's blog footer info
 */
function understrap_posted_on()
{
    $time_string = '%2$s';
    $time_string = sprintf($time_string,
        esc_attr(get_the_modified_date('c')),
        esc_html(get_the_modified_date())
    );
    $modified_date = sprintf(
        esc_html_x('%s ', 'post date', 'understrap'),
        '' . $time_string . ''
    );
    $byline = sprintf(
        esc_html_x('From %s, ', 'post author', 'understrap'),
        '' . esc_html(get_the_author()) . ''
    );
    $by_photo = get_avatar(get_the_author_meta('ID'), 36);
    $user_avatar_url = get_field('user_custom_avatar', 'user_' . get_the_author_meta('ID'));
    if ($user_avatar_url) {
        $by_photo = '<img src="' . $user_avatar_url . '"alt="' . get_the_author_meta('display_name') . '" class="blog-author-photo size-36">';
    }
    echo $by_photo . '<span class="posted-on">' . '</span><span class="byline"> ' . $byline . '</span> updated on: ' . $modified_date;
}

Example 2: Remove the “Powered by WordPress | Theme Name” credit line

Normally the credit line is in the footer.php. When you open the file it is easily to spot this line, maybe in hard-coded text or in a function.

For example, the Accelerate theme holds this line in an action called accelerate_footer_copyright“. You would need to go to footer.php, find and remove this line:

do_action( 'accelerate_footer_copyright' );

Other themes may have this ability with the Customize tool so in some cases you do not need to remove it from the code line.

However, in some cases it is not necessary to remove this line. If the theme is good and you are using it for free, you may want to allow the theme makers to advertise themselves on your website.

Example 3: Add a new page template

A new page template can be added by creating a PHP file name, for example:

  • page-{id}.php: a custom page template for the page with id = {id}
  • page-{about}.php: a custom page template for the web page with slug = about

A page template start with:

<?php 
/** * Template Name: An Example Template * Template Post Type: page */

Then you can save the file as example-template.php. Some themes have a folder page-templates/ (for example Understrap and Accelerate), you can make a template with PHP, HTML, CSS and upload it to the page-templates/example-template.php.

So when adding a new page, you will see the template to be available in the dropdown in Page Attributes.

Find your custom page template in WordPressFind your custom page template in WordPress

This is a great reference: a more detailed guide on how to make a custom page template.

Example 4: Remove custom scripts and styles from plugin/theme

Sometimes we would want to remove the styles and scripts from a plugin, so that we can design from scratch to blend it with the styles of our current website.

WordPress has a wp_dequeue_style() and wp_dequeue_script() functions to remove unwanted stylesheet, or scripts. All you need to do is to find the appropriate handle.

Normally the handle is the id without “-css”. Or, if a plugin has documentation, you can have a look to see what handle they use to enqueue the style, and then dequeue it.

Remove LuckyWP Table of Contents styles

LuckyWP Table of Contents is a plugin that creates table of contents. This provides a better user experience that visitors can immediately jump to the section they want to see. Also, it has some benefits on SEO.

Using Table of Contents to help readers find what they need fastUsing Table of Contents to help readers find what they need fast

Although this plugin also provides a field in the settings for custom CSS, we can save one http request if we remove totally the CSS stylesheet from this plugin from front end, and then include the styles in our child theme’s styles.css.

From the page source of a blog post, you can easily spot the CSS file from the plugin

In this example of LuckyWP Table of Contents, the handle is lwptoc-main. Just place this in your child theme’s functions.php:

wp_dequeue_style('lwptoc-main');

Then come back to View Page Source and you can see that the stylesheet request was removed.

Remove TablePress styles

The case of TablePress is quite simple. The plugin has a hook to remove its default stylesheet.

add_filter( 'tablepress_use_default_css', '__return_false' );

[WordPress] Using Code Snippets to Modify Themes Without Child Theme

Code Snippets is an amazing plugin to add functions to current theme without the need of making a child theme or FTP.

This plugin allows you to add custom code into your website, no matter which theme you are using. This is a great way for function extension, because:

  • Old functions stay when you change themes,
  • Better separation between style (theme) and function (plugin),
  • No need to mess around with functions.php file.

To be able to use this plugin, you will need to have the right to install plugins from WordPress Dashboard.

Examples what you can do with Snippets

  • Add custom tracking code, e.g Google Analytics, Tag Manager, Adsense, Amazon OneTag, Facebook Pixel, etc.
  • Using WordPress actions and filters hooks to change content or add actions.

And when you do not want to have that code anymore, just disable the piece of code and it’s done!

Even better: You can export the code snippets and import it to other WordPress websites.

Using Snippets to easily adding custom to your WordPress website
Using Snippets to easily adding custom functions to your WordPress website

Example 1: Add Google Analytics tracking code with Snippets and add_action hook

Let’s make a new Snippet with this code:

add_action( 'wp_head' , 'ak_add_google_analytics' );
function ak_add_google_analytics() {
    // Add your Google Analytics tracking code here
}

Then choose one of these options:

  • Run snippet everywhere
  • Only run in administration area
  • Only run on site front-end
  • Only run once

For the GA tracking code, we only need to run it on the site front-end.

In this example: we hook into the wp_head action and inject our GA tracking code right in the beginning of <head> tag with priority “1”. After that, delete cache, go to the web browser, right click and “View Page Source” to see if the code is installed successfully.

Example 2: Add MailChimp exit pop-up to WordPress with Snippets

The MailChimp email automation tool provides a useful exit pop-up for a website to collect emails. You can create/config/design the pop ups on MailChimp end and then integrate a piece of Javascript code on your website so it will run automatically.

add mailchimp exit pop-up code to wordpress
Add Mailchimp exit pop-up code to wordpress
add_action( 'wp_footer' , 'ak_add_mailchimp_exit_popup' );
function ak_add_mailchimp_exit_popup() {
    // Add the JavaScript MailChimp code here
}

Then activate the Snippet and choose to run it on front-end site. Similar to the example of Google Analytics, here we inject the code into the footer with the hook wp_footer. Why not use wp_head? We can, but we should not. Unlike the analytics, which should be fired as soon as possible so that it can capture important user behavior e.g events, the exit pop-up has lower priority so that it can wait until all of the website content loads before being triggered.

Example 3: Add a custom message at the end of a post

So this piece of code is helpful if you want to add a small line at the end of all articles. For example, if you have a message that you would like to promote, it is easier to add the message this way, rather than opening all articles and manually adding the text line.

add_action( 'the_content' , 'ak_add_message_content_after' );
function ak_add_message_content_after( $content ) {
    // bail out if the post type is not suitable
    if(!is_singular('post')) return $content;
    $message = 'This will be added at the end of each article.';
    return $content . $message;
}

This time we use add_filter and the_content to modified the content of a page. We need to wrap the code snippet in the conditional is_singular(‘post’) because we do not want to show the message on all the pages, but only on posts.

Auto add a custom message at the end of all WordPress posts.
Auto add a custom message at the end of all WordPress posts.

[WordPress] Let’s Make Plugin E01: A Simple View Count Plugin

Hi, Nice to see you again!

Recently I have learned more about WordPress and feel extremely excited about it so I would like to share this positive energy to all of you. That’s why I think it would be fun to make simple plugins… I mean, to have a solution and to build it in a way that is comprehensive would be fun, even though there are plenty of plugins for view counters already!

And this is the episode 01: to make a simple view count plugin. Hopefully I can keep you until the end of the article 🤣

What we will complete in this episode:

  • Logs view count of different post type into a custom table, exclude admin and post author from the count and exclude articles that are not published
  • Setup a user settings page in admin area to let user select whether or not to display view count and place it at the bottom or top of the article’s content
  • Query the top 5 most-viewed posts with a shortcode so that user can include in widgets or theme
  • Delete view count row of a post after the post gets deleted

My setup:

  • LocalWP to create WordPress test site locally (PHP 8.1.9, MySQL 8.0.16)
  • FakerPress plugin to create dummy text
  • Twenty Twenty-One theme
  • GitHub to store and version-control the plugin folder

This step by step guide is for local development

As I am using LocalWP for local development, I’ll move the whole folder plugin into wp-content/plugins folder, and code then check at the same time as the code directly connect to the WordPress instance. That’s why I repeat through out this post “after this step, we can do this, do that…” But if you don’t have a proper local setup, you may need to finish all the steps, and then activate the plugin to see it works.

Table of contents

  1. 1. The Structure Of A Plugin
    1. 2. Making WordPress Custom Table: Logs View Counts of Different Post Types
      1. 3. Create A Class To Handle Database Insert, Update And Delete
        1. 4. Build a settings page that user can decide whether to display views count in front end, and where to display it
          1. 5. Logs view counts when page load
            1. 6. Display view count snippet on front end
              1. 7. Display top-viewed articles with shortcode
                1. 8. Delete view count records when the article got deleted with deleted_post hook
                  1. 9. Add view counts custom column to admin’s posts page views
                    1. 10. [GitHub] Full code
                      1. 11. Key Takeaways and some final thoughts

                        1. The Structure Of A Plugin

                        For concepts of a plugin, please refer to WordPress official page about making plugins.

                        A plugin is a neat way to add custom functions without touching the WordPress core and not even the theme.

                        Theme should be for style, and plugin for function. Yes we can add custom functions to the functions.php file of the theme as well but after a while it is difficult to scale and extremely annoying to maintain as every kind of functions are stuffed in one file.

                        For this project, let’s call the plugin AK View Count (yes it is by my name but you can change it to yours also 🤣).

                        Before starting to code, let’s plan a bit about our plugin. The structure that I suggest:

                        • [Folder] AK View Count
                          • [Folder] includes: contains classes to handle custom table and hook functions
                            • class-ak-view-counter.php: handling hooks and functions that do not directly touch the database
                            • class-ak-view-counter-db.php: inserting, updating and deleting rows using class $wpdb
                            • class-ak-view-counter-admin.php: adding a view count as custom column in Pages and Posts admin view
                            • class-ak-view-counter-settings.php: add a submenu tool page that users can provide settings to turn on/off the display of view count and select the location (if displaying) in front end
                          • [Folder] templates: contain html template
                            • top-views.php
                            • settings.php
                          • [File] ak-view-counter.php: contains information about the plugin and initial function to create table
                          • [File – optional but recommended] changelog.md: logs about changes of each version

                        Probably good enough… Or we always can change later if there are more optimized way to structure our code which we feel comfortable to work with.

                        Here’s a screenshot of it (please ignore .idea folder, it is generated by PHPStorm).

                        WordPress plugin structure
                        Plugin structure

                        Let’s get down to code.

                        2. Making WordPress Custom Table: Logs View Counts of Different Post Types

                        There is a great article about making custom table by DeliciousBrain: Creating a Custom Table with PHP in WordPress that I highly recommend to read.

                        Now you may wonder why we don’t just log the view count by adding a post meta to each posts / pages and just display it? It is much simpler than creating a custom table because:

                        • Can use at once update_post_meta() and get_post_meta() of WordPress’s built in functions without writing extra codes ro handle database stuff,
                        • Automatically get deleted when the article is deleted as it is a post meta

                        Yes, I agree that it is very fast way to make this work, and it should be considered as one method, too. But when there are thousands of posts, a separate table of view counts would make the process of querying and filtering among post types and getting top results faster.

                        The custom table’s structure would look like:

                        Table schema of AK view counter wordpress plugin
                        Table schema of AK view counter plugin

                        Based on your need, there could be timestamp column also, in case you want to log the latest timestamp a post gets view, or the publish date of the post so that the most-viewed posts can be queried and filtered by the time it is published.

                        But in this article, let’s just start with this simple structure, storing only post_id, post_type and views.

                        To create the table, add a function with register_activation_hook(), to create a view count table when the plugin is activated.

                        In ak-view-counter.php file:

                        <?php
                        /**
                         * Plugin Name: AK View Counter
                         * Description: Dead simple counter for WordPress posts and pages
                         * Version: 1.0.1
                         * Author: Anh Karppinen
                         * Text Domain: ak-view-counter
                        **/
                        if (!defined('ABSPATH')) exit;
                        define('AK_VIEW_COUNTER_DIR', plugin_dir_path(__FILE__));
                        register_activation_hook(__FILE__, 'ak_create_view_count_table');
                        /**
                         * Create view count custom table when plugin is activated
                         */
                        function ak_create_view_count_table() {
                        	global $wpdb;
                        	$charset_collate = $wpdb->get_charset_collate();
                        	$page_views_table = "CREATE TABLE IF NOT EXISTS `{$wpdb->base_prefix}ak_viewcount` (
                        		viewcount_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
                        		post_id bigint(20) UNSIGNED NOT NULL,
                            	        post_type varchar(100) NOT NULL,
                        		views bigint(20) UNSIGNED NOT NULL,
                        		PRIMARY KEY  (viewcount_id),
                        		UNIQUE `post_id` (post_id),
                        		KEY `post_type` (post_type)
                        	) $charset_collate;";
                        	require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
                        	dbDelta($page_views_table);
                        }
                        require_once(AK_VIEW_COUNTER_DIR . '/includes/class-ak-view-counter-db.php');
                        require_once(AK_VIEW_COUNTER_DIR . '/includes/class-ak-view-counter.php');
                        require_once(AK_VIEW_COUNTER_DIR . '/includes/class-ak-view-counter-settings.php');
                        require_once(AK_VIEW_COUNTER_DIR . '/includes/class-ak-view-counter-admin.php');
                        

                        Now go back to WP dashboard, install the plugin and activate it. Then go to your database and check if the custom table is successfully created.

                        wordpress custom table for view count

                        3. Create A Class To Handle Database Insert, Update And Delete

                        In file class-ak-view-counter-db.php, let’s visual the handler like:

                        <?php
                        /**
                         * Handling view count data from custom table
                         */
                        if (!defined('ABSPATH')) exit;
                        class AK_View_Counter_Db {
                            const VIEW_COUNT_TABLE = 'ak_viewcount';
                            public static function count_up(int $post_id, string $post_type) {}
                            public static function get_view_count(int $post_id) {}
                            public static function get_top_views(string $post_type) {}
                            public static function delete_view_count_row(int $post_id) {}
                        }

                        For counting up, we’ll write a SQL command to insert 1 new view when no duplicated post_id is found. As we set the post_id to be UNIQUE in the database schema, we can use ON DUPLICATE KEY UPDATE, which means if no same unique key was found: create new row; if found: update:

                        /**
                         * Counts up
                         * @param int $post_id
                         * @param string $post_type
                         * @return false|int
                        */
                        public static function count_up(int $post_id, string $post_type) {
                                if(empty($post_id) || empty($post_type)) return false;
                                global $wpdb;
                                $sql = "INSERT INTO " . $wpdb->base_prefix . self::VIEW_COUNT_TABLE . " (post_id, post_type, views) VALUES (%d, %s, %d) ON DUPLICATE KEY UPDATE views = views + %d";
                                return $wpdb->query( $wpdb->prepare($sql, $post_id, $post_type, 1, 1));
                        }

                        For the rest of this database middleman we can use functions of $wpdb class.

                        /**
                         * Get the view count of a specific post
                         * @param int $post_id
                         * @return false|int
                        */
                        public static function get_view_count(int $post_id) {
                                if(empty($post_id)) return false;
                                global $wpdb;
                                $query = $wpdb->get_results($wpdb->prepare("SELECT views FROM " . $wpdb->base_prefix . self::VIEW_COUNT_TABLE . " WHERE post_id = %d",
                                    $post_id
                                ), ARRAY_A);
                                if(empty($query)) return 0;
                                return (int) wp_list_pluck($query, 'views')[0];
                        }

                        A nice built-in function!

                        wp_list_pluck() is a very useful WordPress function, which fastly pulled out an array of one user-defined field from an original array. See more: https://developer.wordpress.org/reference/functions/wp_list_pluck/

                        Function to get the top views (maximum 5):

                        /**
                         * Get top five most-viewed by post type
                         * @param string $post_type
                         * @return array
                        */
                        public static function get_top_views(string $post_type = '') {
                            global $wpdb;
                            $table = $wpdb->base_prefix . self::VIEW_COUNT_TABLE;
                            if($post_type) {
                        	$top_views_query = $wpdb->prepare("SELECT post_id, views FROM " . $table . " WHERE post_type = %s ORDER BY views DESC LIMIT 5", $post_type);
                           } else {
                        	$top_views_query = "SELECT post_id, views FROM " . $table . " ORDER BY views DESC LIMIT 5";
                           }
                           return $wpdb->get_results($top_views_query, ARRAY_A);
                        }

                        And the last one to delete a row in our table:

                        /**
                         * Remove the row of view count by post id
                         * @param int $post_id
                         * @return false|int
                        */
                        public static function delete_view_count_row(int $post_id) {
                            if(empty($post_id)) return false;
                            global $wpdb;
                            return $wpdb->delete($wpdb->base_prefix . self::VIEW_COUNT_TABLE, ['post_id' => $post_id], ['%d']);
                        }

                        So that’s it, that’s all the functions to communicate with the custom table in the database! We can later use them through out the plugin, in other classes.

                        Data type validation in $wpdb functions

                        For the integrity of data type, we should provide the format when inserting / updating data in the database. See more from the $format parameters in https://developer.wordpress.org/reference/classes/wpdb/insert/

                        The $wpdb class by default return an object. I prefer array so I set it to ARRAY_A, but you don’t have to. However, if no ARRAY_A provided, you need to access data like $tv->post_id instead of $tv[‘post_id’].

                        4. Build a settings page that user can decide whether to display views count in front end, and where to display it

                        It is quite straightforward to make a settings page. All we need to do is to hook into admin_init and admin_menu to add our custom settings page under the Tools in admin area and link the form input’s submission to wp_options table.

                        In class-ak-view-counter-settings.php, add these code:

                        <?php
                        /**
                         * Handling user settings / options
                         */
                        if(!defined('ABSPATH')) exit;
                        class AK_View_Counter_Settings {
                            public static function init() {
                                add_action('admin_menu', array(__CLASS__, 'add_view_count_option_page'));
                        	add_action('admin_init', array(__CLASS__, 'register_options'));
                            }
                            /**
                             * Add an option page that user can select the location of the view counter
                             * Page can be access as a sub-menu under Tools Menu
                             */
                            public static function add_view_count_option_page() {
                                add_submenu_page(
                                    'tools.php',
                                    'AK View Counter',
                                    'AVC Settings',
                                    'administrator',
                                    'ak-view-counter-settings',
                        	    array(__CLASS__, 'register_counter_settings')
                                );
                            }
                            /**
                             * Include settings page HTML template
                             */
                            public static function register_counter_settings() {
                                include_once(AK_VIEW_COUNTER_DIR . '/templates/settings.php');
                            }
                            /**
                             * Add recognization for setting fields that will be added to options
                             */
                            public static function register_options() {
                        	register_setting('ak-view-count', 'ak_view_count_display');
                        	register_setting('ak-view-count', 'ak_view_count_location');
                            }
                        }
                        AK_View_Counter_Settings::init();
                        

                        In template file settings.php:

                        <?php
                        /**
                         * Settings page UI
                         * Let user turns on/off view counter from front end and selects the location of view counter
                         */
                        if(!defined('ABSPATH')) exit;
                        $is_display = get_option('ak_view_count_display');
                        $display_position = get_option('ak_view_count_location');
                        ?>
                        <div class="wrap">
                            <h1>AK View Counter Settings</h1>
                            <form method="post" action="options.php">
                        	    <?php settings_fields('ak-view-count'); ?>
                        	    <?php do_settings_sections('ak-view-counter-settings'); ?>
                                <table class="form-table">
                                    <tr valign="top">
                                        <th scope="row">Display</th>
                                        <td>
                                            <input type="checkbox"
                                                   name="ak_view_count_display"
                                                   value="1"
                                                   <?php if($is_display) echo 'checked'; ?> />
                                        </td>
                                    </tr>
                                    <tr valign="top">
                                        <th scope="row">Location</th>
                                        <td>
                                            <input type="radio"
                                                   id="ak_view_count_location"
                                                   name="ak_view_count_location"
                                                   value="top"
                                                  <?php if($display_position === 'top') echo 'checked'; ?> />
                                            <label for="ak_view_count_location">Top</label><br>
                                            <input type="radio"
                                                   id="ak_view_count_location"
                                                   name="ak_view_count_location"
                                                   value="bottom"
                                                   <?php if($display_position === 'bottom') echo 'checked'; ?> />
                                            <label for="ak_view_count_location">Bottom</label>
                                        </td>
                                    </tr>
                                </table>
                                <?php submit_button(); ?>
                            </form>
                        </div>
                        

                        And now if you go to Dashboard > Tools, you can see the submenu here.

                        Submenu settings page under Tools - WordPress dashboard
                        Submenu settings page under Tools – WordPress dashboard

                        Confirm that when changing the Display and Location, and clicking Submit, the page reloads and successfully remember your choice. To have a deeper look, let’s access the database and see that the option has been saved nicely.

                        WordPress database for options
                        WordPress database for options

                        Later on, we will access these settings to decide the condition to show the view count.

                        5. Logs view counts when page load

                        Now that we have completed building the database functions and settings page, let’s come to the main part.

                        First, let’s define the skeleton to class-ak-view-counter.php, which will defines functions to:

                        • Count up,
                        • Display view counts in front end,
                        • Register shortcodes for listing top views articles,
                        • Remove view count row after an article is deleted

                        For those functions, the WordPress hooks that we are going to use:

                        • wp: fires once the WordPress environment has been set up, when we can access the post by get_the_ID()
                        • deleted_post: fires after a post has been deleted from the database (not trash)
                        • init: fires after WordPress finish loading, to register the shortcode
                        • the_content: a filter to modify the article’s content before displaying it, to put in our views snippet

                        Warning

                        You can use “wp” hook, or any other hooks that runs allowing to use get_the_ID(), but be careful with the main or secondary query. For example, using “template_redirect” without filtering main query will result into adding view counts for two different posts at the same time!

                        Again, let’s first wrap everything up in a class.

                        <?php
                        /**
                         * Register view count into database
                         */
                        if(!defined( 'ABSPATH')) exit;
                        if(!class_exists('AK_View_Counter_Db')) return;
                        class AK_View_Counter {
                           public static function init() {
                               add_action('wp', array(__CLASS__, 'count_up'));
                               add_action('deleted_post', array(__CLASS__, 'remove_view_count'));
                               add_action('init', array(__CLASS__, 'register_top_view_shortcode'));
                               add_filter('the_content', array(__CLASS__, 'show_views'));
                           }
                           /**
                            * Increase views
                            * Happens after WP environment has finished setup and is deciding which template to load
                            * Post ID and post type are accessible at this point
                            */
                            public static function count_up() {
                                // Do not count if this is admin area or not single page
                                // Do not count if page author or admin is visiting
                                // Do not count posts that are not published
                                // Finally call for count_up function
                           }
                           /**
                            * Display html code to show view count in front end
                            */
                            public static function show_views($content) {
                        	// Don't show if it is not a single page
                        	// Don't show if user selects not to show
                            }
                            /**
                             * Register a shortcode so that user can include it in for example widgets
                             * Shortcode: [ak-top-views]
                             */
                            public static function register_top_view_shortcode() {
                               add_shortcode( 'ak-top-views', array(__CLASS__, 'display_top_views') );
                            }
                           /**
                             * Get shortcode attributes and display the list on front end
                             * @param $atts
                             * @return false|string
                             */
                            public static function display_top_views($atts) {}
                           /**
                            * Delete the view count data together with the deletion of a post
                            * @params $post_id
                            */
                           public static function remove_view_count($post_id) {}
                        }
                        AK_View_Counter::init();

                        Following the plan, function count_up would look like this:

                        /**
                         * Increase views
                         * Happens after WP environment has finished setup
                         * Post ID and post type are accessible at this point
                         */
                         public static function count_up() {
                            // Do not count if this is admin area or not single page
                            if(is_admin() || ! is_singular()) return;
                            $post_id = get_the_ID();
                            if(!$post_id) return;
                            // Do not count if page author or admin is visiting
                            $post_author = get_post_field('post_author', $post_id);
                            $current_user_id = get_current_user_id();
                            if($current_user_id == $post_author) return;
                            // Do not count posts that are not published
                            $post_status = get_post_status();
                            if($post_status !== 'publish') return;
                            // Finally call for count_up function
                            AK_View_Counter_Db::count_up($post_id, get_post_type());
                         }

                        At this moment, the database should log view count everytime a post / page is loaded.

                        Views count table at a glance after loading several pages
                        Views count table at a glance after loading several pages

                        6. Display view count snippet on front end

                        Now we have the data, how to show up on front end in a single post? We already create a hook function:

                        add_filter( 'the_content', array(__CLASS__, 'show_views' ) );

                        Now map it:

                        /**
                         * Display html code to show view count in front end
                         * @params $content
                         */
                        public static function show_views($content) {
                            // Don't show if it is not a single page
                            if(!is_singular()) return $content;
                            // Don't show if user wants to hide
                            $is_display = get_option('ak_view_count_display');
                            if(!$is_display) return $content;
                            $location = get_option('ak_view_count_location');
                            $views = AK_View_Counter_Db::get_view_count(get_the_ID());
                            $view_count_text = "<div class='ak-view-counter'>Views: " . $views . '</div>';
                            if($location) {
                                 if($location === 'top') {
                                     return $view_count_text . $content;
                                 }  else  {
                                     return $content . $view_count_text;
                                 }
                            }
                            return $content;
                        }

                        Warning

                        When using the_content filter, remember to return the $content no matter if condition matches or not, or… soon you will see why 🤣

                        Try to test changing the Display and Location in Settings page. It should display / not display before / after the post content like selected. If not… um there probably are some bugs around.

                        Displaying views in the top of content block
                        Displaying views in the top of content block

                        7. Display top-viewed articles with shortcode

                        Show the template of top views:

                        /**
                         * Display the list on front end
                         * @param $atts
                         * @return false|string
                         */
                        public static function display_top_views($atts) {
                            $atts = shortcode_atts( array(
                                 	  'post_type' => '',
                        	    ), $atts );
                            $top_view = AK_View_Counter_Db::get_top_views($atts['post_type']);
                            if(!$top_view) echo "No views.";
                           // Setup the array which contain titles and links of selected top views
                           $top_view_articles = [];
                                foreach($top_view as $tv) {
                        	     $top_view_articles[] = array(
                        		  'title' => apply_filters('the_title', get_the_title($tv['post_id'])),
                        		  'link' => esc_url(get_the_permalink($tv['post_id'])),
                        		  'view' => (int) $tv['views']
                        	     );
                        	}
                        	ob_start();
                        	include(AK_VIEW_COUNTER_DIR . '/templates/top-views.php');
                                return ob_get_clean();
                        }

                        The template for top views (top-views.php):

                        <?php
                        /**
                         * Top view count shortcode template
                         */
                        if(!defined('ABSPATH')) exit;
                        if($top_view_articles) :
                        ?>
                        <ul>
                            <?php foreach($top_view_articles as $key=>$articles) : ?>
                        	<li>
                        	   <?php echo $key + 1; ?>.
                        	  <a href="<?php echo $articles['link']; ?>">
                                     <?php echo $articles['title']; ?>
                                  </a>
                        	<li>
                            <?php endforeach; ?>
                        </ul>
                        <?php endif;

                        After this step, the plugin should be successfully display the top five articles where the shortcode [ak-top-views] is inputted. Let’s go to Widgets section, select a Shortcode type of widget and add [ak-top-views] there. It should show up where the widget is.

                        8. Delete view count records when the article got deleted with deleted_post hook

                        When a post / page / any custom post type gets deleted from the database, we should remove the record also from view counts table, so that the database don’t get bloated with unused records after a time of using.

                        We already provide a function for deleted_post hook:

                        add_action( 'deleted_post', array(__CLASS__, 'remove_view_count' ) );

                        And we can map the action with the removal action in AK_View_Counter_Db class.

                        /**
                         * Delete the view count data together with the deletion of a post
                         * @params $post_id
                         */
                        public static function remove_view_count($post_id) {
                            AK_View_Counter_Db::delete_view_count_row($post_id);
                        }

                        It’s quite easy to test the function. Just try to delete (not just trash) an article which already have results in wp_ak_viewcount table and the delete actions should remove the row with corresponding post_id also.

                        9. Add view counts custom column to admin’s posts page views

                        The last thing to do is to add into admin column view, a custom column that list the view counts. It would look like this column:

                        wordpress add custom column to posts view

                        To be able to achieve this, we will use two hooks in admin area. For post:

                        • manage_posts_columns: filters the columns displayed in the Posts list table.
                        • manage_posts_custom_column: fires when WordPress renders each column in Posts list table

                        And for page:

                        • manage_page_posts_columns
                        • manage_page_posts_custom_column

                        If you have another custom post type, do it for them also:

                        • manage_{custom-post-type-slug}_posts_custom_column
                        • manage_{custom-post-type-slug}_posts_custom_column

                        In class-ak-view-counter-admin.php file:

                        <?php
                        /**
                         * Handling admin area
                         */
                        if ( ! defined( 'ABSPATH' ) ) exit;
                        if ( ! class_exists('AK_View_Counter_Db')) return;
                        class AK_View_Counter_Admin {
                           public static function init() {
                        	add_filter('manage_posts_columns', array(__CLASS__, 'add_view_count_column'));
                        	add_filter('manage_page_posts_columns', array(__CLASS__, 'add_view_count_column'));
                        	add_action('manage_posts_custom_column', array(__CLASS__, 'view_count_column_content'), 10, 2);
                        	add_action('manage_page_posts_custom_column', array(__CLASS__, 'view_count_column_content'), 10, 2);
                           }
                           /**
                             * Register view count column in admin view
                            */
                            public static function add_view_count_column($columns) {
                        	$columns['view_count'] = 'Views';
                        	return $columns;
                            }
                            /**
                             * Put values to view count column
                             */
                            public static function view_count_column_content($column, $post_id) {
                        	if('view_count' === $column) {
                        	    echo AK_View_Counter_Db::get_view_count($post_id);
                        	}
                            }
                        }
                        AK_View_Counter_Admin::init();
                        

                        That’s it! Come back to the listing table to see that the Views column has been there!

                        10. [GitHub] Full code

                        GitHub repo: https://github.com/vuongngocanh189/ak-view-count-plugin/blob/main/README.md

                        11. Key Takeaways and some final thoughts

                        After this articles, we have practiced:

                        • How to create custom table and handle database actions with $wpdb class
                        • How to create a settings page in WordPress
                        • How to create a shortcode to show top viewed articles
                        • How to use WordPress hooks in admin area and in front end to add our custom function

                        Please notice this is just like a starting brick to a ready-to-use plugin. To make it ready for production, it would need to be more secure, for example to avoid counts from bot visits, or to limit the views from each IP address, and so on. And of course, we haven’t made any styling at all.

                        But I hope you enjoy this and don’t be hesitate to join the discussion!

                        [WordPress] Create custom post type programmatically

                        In this article, let’s talk about custom post types. What are they, why we need them, how to create them programmatically, and have a look on its anatomy in case you want to have custom design templates for the pages generated by the custom post type.

                        What is custom post type and do I need it?

                        By default, WordPress gives us several post types: page, post, attachment, navigation menu, revisions, custom css and changesets. Among them, Pages and Posts are probably the most recognizable ones.

                        You would probably asks yourself several times, “Do I need a custom post type? Can things be ok with just using pages or posts?” If that question still hanging on your head, I would suggest to create custom post type if:

                        • You need to filter and query the post type articles separately from other post types, and have to do it frequently, via WordPress WP_Query or via Rest API endpoints
                        • It needs custom taxonomies which should be filtered and queried, too.
                        • You don’t mind creating a bunch of templates to support the custom post type’s single page, archive page and taxonomies page. It’s optional though, because without them, WordPress will use the style from the current theme. But in many cases you would want to have custom designs for those generated by custom post type, and you would need to know how to do it.

                        In the other side, if you just want to post articles which have some extra fields that does not yet have in Posts and Pages, then it’s easier to create custom meta fields> and attach to posts / pages instead.

                        Notices:

                        • There will be a difference in slugs. If you use WordPress built-in Pages and Posts, the slug of a single article / page can be http(s)://domainname.com/{name-of-the-article}. If you access a a custom post type single page, it will be http(s)://domainname.com/{custom-post-type-base}/{name-of-the-article}. So it would somehow affect SEO if think about how the slug that include keyword should be close to the domain name.
                        • The custom post type will be saved together with other post types in wp_posts table

                        If now you already decided that you need custom post types, not custom meta fields, then read on!

                        How to add custom post type programmatically in WordPress

                        If you don’t want to touch the code, it’s fine to just use any plugin to do this. But, creating the custom post type programmatically is pretty easy. And below is how you can do it.

                        To make things easier to imagine, let’s say we have a private clinic website and would need to create the new custom post type to display doctor profiles.

                        Warning

                        As WordPress suggests, do not add the code in your theme’s file. That way, if you switch themes, you will lose the custom post type. But, if you create a theme by yourself and do not have a plan to change it in the whatsoever future, then the code can be in your theme.

                        // WordPress suggests adding to 'init' hook and not before, so let's go with it
                        add_action( 'init', 'ak_create_doctor_profiles_cpt' );
                        function ak_create_doctor_profiles_cpt () {
                            register_post_type( 'doctor', 
                                array(
                                   'labels' => array(
                                        'name' => 'Doctors',
                                        'singular_name' => 'Doctors',
                                        'description' => 'A portfolio of current doctors in the clinic'
                                   ),
                                   'has_archive' => true,
                                   'menu_icon' => 'dashicons-admin-users',
                                   'supports' => array('title', 'editor', 'thumbnail'),
                                   'public' => true,
                                   'rewrite  => array(
                                       'slug' => '', // Add this only if you need a custom slug base
                                       'with_front' => false
                                   )
                                )
                            );     
                        }
                        

                        This WordPress page about custom post types explains the parameters very well so please read it.

                        Warning

                        You don’t need to explicitly set all parameters, but you must explicitly set public, or else your post type won’t show up in the Admin Dashboard.

                        Here, let’s go through some useful one:

                        • has_archive: can be set to false if you want to disable the front page http(s)://domainname.com/doctor/
                        • supports: by default, WordPress will give ‘title’, ‘editor’, ‘comments’, ‘revisions’, ‘trackbacks’, ‘author’, ‘excerpt’, ‘page-attributes’, ‘thumbnail’, ‘custom-fields’, and ‘post-formats’. Only some of them are necessary, so we only include here title (doctor name), editor (description), thumbnail (probably a photo).
                        • rewrite, slug: rewrite the base that is used in http(s)://domainname.com/{base}/{single-record-slug}. It is useful if you want it to be different language than “doctor” – the custom post type name that WordPress will use.
                        • dashicons: the icon that shows up in WordPress dashboard.

                        Creating custom taxonomies programmatically

                        What’s custom post types without custom taxonomies? So let’s create one also.

                        Supposed that we need a categorized page for doctors based on their expertise (cardiologists, dermatologists, dentists, allergists).

                        And so when user visits page http(s)://domainname.com/expertise/cardiologists for example, they will see a list of doctors who can solve your blood and heart issues.

                        To create custom taxonomies programmatically, just continue with the function ak_create_doctor_profiles_cpt(), add the function register_taxonomy() below the register_post_type() function:

                        register_taxonomy( 'expertise', 
                                array('doctors'),
                                array(
                                   'labels' => array(
                                        'name' => 'Expertise',
                                        'singular_name' => 'Expertise',
                                        'menu_name' => 'Expertises'
                                   ),
                                   'rewrite  => array(
                                       'slug' => '', // Add this only if you need a custom slug base
                                       'with_front' => false
                                   )
                                )
                            );      
                        }
                        

                        Again, WordPress Codex provides really helpful info about how to set your own config. They are quite straightforward, but here is one tricky part if you haven’t noticed: the public parameter will affect other parameters like publicly_queryable, show_ui and show_in_nav_menus. So if you would like to change the visibility of the taxonomy, use the combo carefully.

                        Creating template files for custom post type

                        Before going deeper, let’s have a look of the anatomy of custom post type pages. In our “doctor” example, it will be like:

                        • Archive page: can be accessed via http(s)://domainname.com/doctor/
                        • Single page: for every record that you create with the custom post type, it can be seen via http(s)://domainname.com/doctor/{single-record-slug}/
                        • Taxonomy page: for every term (e.g cardiologists, dermatologists, dentists, allergists) that is created, the page that collects all records that had the term can be accessed via http(s)://domainname.com/expertise/{term-slug}

                        Now let’s see how you can create and map the correct template to each of the types mentioned above.

                        Information

                        It is not mandatory to create custom files for these pages. If you don’t make, WordPress will get the template from your current theme’s design, or more specific, from these files: archive.php, single.php and taxonomy.php.

                        Archive page

                        To make a custom archive page, create a PHP file and name it archive-doctor.php. WordPress system will automatically detect it and load it when your archive loads.

                        Single page

                        Similar to the way to create archive page, just create single-doctor.php and implement your design there.

                        Taxonomy page

                        The name for taxonomy page that WordPress can map the template is taxonomy-expertise.php.

                        How to query the custom post type

                        WP_Query() and get_posts() both have a parameter that you can filter the post type to return. Here is the examples from both:

                        For WP_Query():

                        $args = array(
                            'post_type' => 'doctor',
                            'post_status' => 'publish',
                            'posts_per_page' => 20,
                        );
                        $doctor_profiles = new WP_Query($args);
                        if ( $doctor_profiles->have_posts ) {
                            while ( $doctor_profiles->have_posts ) {
                                $doctor_profiles->the_post();
                                // Do something with the records
                            }
                            
                            wp_reset_postdata();
                        }
                        

                        For get_posts():

                        $args = array(
                            'post_type' => 'doctor',
                            'post_status' => 'publish',
                            'numberposts' => 20, // Default of get_posts is 5, so remember to set this value if you want to have more than 5 records returned
                        );
                        if ( ! empty($doctor_profiles) ) {
                            foreach ( $doctor_profiles as $profile )
                            {
                                // Do something with the records
                            }
                        }
                        

                        Visually: how it looks like in WordPress admin dashboard

                        So here’s how you can see the newly created custom post type.

                        creating custom post type programmatically wordpress

                        That’s it! Simple as life. Bye and see you in the next article!

                        [WordPress] Jetpack Blocks Admin Login Solution: Whitelist Your IP

                        I don’t care much about Jetpack’s new security method until I was locked out of my dashboard this afternoon. Jetpack probably thought that I was trying to hack on something due to multiple log-in activities throughout my WordPress sites.

                        Clearly, they are trying to separate between good guys and bad ones based on the number of sites they are trying to access wp-admin area at the same time.

                        Well, that’s a good criterion to base on. However, since I have many sites, the multiple login activities are inevitable. Although Jetpack sent me an email to reattempt my login, the button in the email did not work. It said “bad request”. So sad.

                        So, I have to add my IP to the Jetpack whitelist to prevent future blocks. Ah wait… if I was already blocked, how could I do anything? Just follow these three simple steps:

                        Deactivate Jetpack from your web folder

                        So you have cPanel or FTP access, right?

                        For FTP user, just go to your WordPress folder > wp-content > plugins.

                        You will see the Jetpack from the list. Simply rename it Jetpack-disabled. And you’re done. The plugin is disabled.

                        If you choose to visit cPanel, also navigate to your web folder through File Manager > public_html > your WordPress site > wp-content > plugins.

                        And similarly, rename the Jetpack to Jetpack-disabled.

                        So simple. I love simple things.

                        Login again to your site

                        Since Jetpack is disabled, you will be able to login normally to your dashboard again.

                        Activate Jetpack from your dashboard

                        Since you are already login, activating Jetpack won’t get you locked out of the site again.

                        Go to Plugins > Jetpack and click activate.

                        Whitelist your IP in Jetpack settings

                        When Jetpack is activated, go to Jetpack > Settings > Security.

                        Expand the Brute force attack protection. You will see your IP address listed there. Simply click Add to Whitelist. 

                        And don’t forget to Save Settings.

                        You’re done. Your IP is now safe.

                        Jetpack block admin login solution whitelist your ip
                        Make sure that the IP you use to login is in the Jetpack’s safe zone.
                        • Go to page 1
                        • Go to page 2
                        • Go to page 3
                        • Interim pages omitted …
                        • Go to page 7
                        • Go to Next Page »

                        Primary Sidebar

                        Hi! I am a Vietnamese coder living in Oulu, Finland. Currently I am working with PHP, MySQL, HTML, CSS, and JavaScript. This blog is my learning diary, in which I share knowledge from my own experience or research. Hopefully you can find something useful here and don’t hesitate to open a discussion or leave a feedback!

                        My Blog

                        A learning diary
                        #wordpress #javascript #php #vue #css

                        Highlights

                        • [WordPress] 5 Ways to Migrate Sites
                        • [WordPress] Customizing Themes: Basic Principles & Examples
                        • reCaptcha v3: How to implement on WordPress custom forms to stop bot signups
                        • [WordPress] How to make the functions.php less messy
                        • [CSS] Simple parallax [Code + Demo]

                        Latest

                        • WordPress + Gumlet + S3: Resizing images from server side and upload to S3
                        • [WordPress] Using Child Themes
                        • [WordPress] Using Code Snippets to Modify Themes Without Child Theme
                        • [WordPress] Let’s Make Plugin E01: A Simple View Count Plugin
                        • [WordPress] Create custom post type programmatically
                        Anh Karppinen, personal web, version 1.0.0
                        • Privacy Policy