Really interesting and quite technical question.
The following is not production ready. It is a working proof of concept.
I'm just pondering here. You could create a new Table (see Creating Tables with Plugins with a requested_url
key and a shortened_url
value. The whole system would be based on that approach.
First we creates a custom table in the database, if it doesn’t already exist. This table will be used to store our urls.
<?php
add_action( 'after_switch_theme', function () {
global $wpdb;
$table_name = $wpdb->prefix . 'wpso74035985';
$charset_collate = $wpdb->get_charset_collate();
$create_ddl = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
requested_url varchar(55) DEFAULT '' NOT NULL,
shortened_url varchar(55) DEFAULT '' NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
maybe_create_table( $table_name, $create_ddl );
} );
We could then retrieve the requested url by parsing the $_SERVER['QUERY_STRING']
and verifying the url integrity through get_query_var() as it only retrieves public query variables that are recognized by WP_Query
.
We would then search the table for the requested url, and if it doesn't already exist, set a new key pair value.
<?php
add_action( 'pre_get_posts', function ( $wp_query ) {
if ( ! is_admin() && $wp_query->is_main_query() ) {
if ( $wp_query->is_search() ) {
global $wp;
parse_str( filter_input( INPUT_SERVER, 'QUERY_STRING', FILTER_SANITIZE_STRING ), $variables );
$buffer = array();
foreach ( $variables as $variable => $value ) {
if ( get_query_var( $variable ) ) {
array_push( $buffer, array( $variable => $value ) );
};
};
$buffer = array_reduce( $buffer, 'array_merge', array() );
$requested_url = esc_url_raw( add_query_arg( $buffer, home_url( $wp->request ) ) );
global $wpdb;
$table_name = $wpdb->prefix . 'wpso74035985';
$results = $wpdb->get_results(
"SELECT * FROM $table_name"
);
$needle_key = array_search( $requested_url, array_column( $results, 'requested_url' ) );
if ( $needle_key === false ) {
$shortened_url = str_shuffle( base64_encode( random_bytes( 100 ) ) );
$shortened_url = preg_replace( '/\W/s', '', $shortened_url );
$shortened_url = substr( $shortened_url, 0, 7 );
$wpdb->insert(
$table_name,
array(
'requested_url' => sanitize_url( $requested_url ),
'shortened_url' => sanitize_url( home_url( $shortened_url ) ),
)
);
};
};
};
} );
There is no real science here, the $shortened_url
is just a 7 bytes alpha numerical string (Why 7? Bit.ly a famous shortener is using 7 characters).
Through the template_redirect
hook, we could check the shortened url against our table and redirect based on that.
<?php
add_action( 'template_redirect', function () {
if ( is_404() ) {
global $wp;
$shortened_url = home_url( $wp->request );
global $wpdb;
$table_name = $wpdb->prefix . 'shortener';
$results = $wpdb->get_results(
"SELECT * FROM $table_name"
);
$needle_key = array_search( $shortened_url, array_column( $results, 'shortened_url' ) );
if ( $needle_key !== false ) {
$location = $results[$needle_key]->requested_url;
wp_safe_redirect( $location );
exit();
};
};
} );
As we're checking for a 404 through is_404()
first (pretty much limiting the number of empty request), you should also include a 404.php
to your root.
On the front end you can access your table through
<?php
global $wpdb;
$table_name = $wpdb->prefix . 'wpso74035985';
$results = $wpdb->get_results(
"SELECT * FROM $table_name"
);
var_dump( $results );
Now, a few thing to understand before you go to production with this, even tho we're generating a pseudo random string through random_bytes()
and base64_encode()
they're not actually fully randomized, so you could end up with a problem at some point where two shortened url are the same. You could do a while loop and constantly check if that shortened url already exist before using it.
You would also want to restrict the search query as much as you can to improve performance on the long term.
You might want also to add an expiry date.