Skip to content

Instantly share code, notes, and snippets.

@jotazzu
Last active January 31, 2024 10:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jotazzu/e9571973ad91877eb9e77839ca9e29c0 to your computer and use it in GitHub Desktop.
Save jotazzu/e9571973ad91877eb9e77839ca9e29c0 to your computer and use it in GitHub Desktop.
This class allows to use WordPress plugin Redirection to add/remove redirects programmatically. It should be included in a custom plugin as it only works in the WP backend.
<?php
/**
* Creates and removes redirects for a post slug
*
* Creates and removes entries in the redirect list of plugin 'Redirection'.
*
* @link https://gist.github.com/jotazzu
* @since 1.0.1
*
* @package Programmable_Redirects
* @subpackage Programmable_Redirects
*/
/**
* Creates and removes redirects for a post slug
*
* Creates and removes entries in the redirect list of plugin 'Redirection'.
*
* @since 1.0.1
* @package Programmable_Redirects
* @subpackage Programmable_Redirects
* @author Josh Taubriler
*/
class Programmable_Redirects {
/**
* Create a new redirection group if not exists using the PHP API of plugin Redirection.
* Returns the ID of the redirection group if Redirection API is available and the group exists
* or has successfully been created.
*
* @param string $redir_group_name Name of the redirection group to check or create if not exists.
*
* @return int|WP_Error. ID of the found redirection group else WP_Error.
*
* @since 1.0.1
*/
public static function add_redirection_group( $redir_group_name = '' ) {
// Check if API of plugin Redirection is available!
if ( !method_exists( 'Red_Group', 'get_all') ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Group::get_all()' of plugin Redirector does not exist in " . __METHOD__ ); }
if ( !method_exists( 'Red_Group', 'create' ) ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Group::create()' of plugin Redirector does not exist in " . __METHOD__ ); }
if ( !method_exists( 'Red_Group', 'get_id' ) ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Group::get_id()' of plugin Redirector does not exist in " . __METHOD__ ); }
if ($redir_group_name != '') {
$group_filter = array(
'filterBy' => [
'name' => $redir_group_name,
],
);
$redir_groups = Red_Group::get_all( $group_filter );
if ( empty($redir_groups) ) {
// Create a redirect group
$group_obj = Red_Group::create( $redir_group_name, $module_id=1 );
if ( $group_obj !== false) {
return $group_obj->get_id();
} else {
return new WP_Error( 'programmable_redirection_redirects', "Creating new redirection GROUP '{$redir_group_name}' has failed in " . __METHOD__ );;
}
}
return $redir_groups[0]['id'];
}
return new WP_Error( 'programmable_redirection_redirects', "No group name provided for creating a new redirection GROUP: \$redir_group_name='{$redir_group_name}' in " . __METHOD__ );
}
/**
* Retrieve existing redirects for the given post.
*
* @param WP_Post $post The post object or ID.
*
* @return $redir_items Array|WP_Error An (empty) array with already existing redirects or WP_Error in case of failure.
*
* @since 1.0.1
*/
public static function get_redirection_redirects( $post ) {
// Check if API of plugin Redirection is available!
if ( !method_exists( 'Red_Item', 'get_filtered') ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Item::get_filtered()' of plugin Redirector does not exist in " . __METHOD__ ); }
$post_relative_url = str_replace( home_url(), '', get_permalink( $post ) );
$post_relative_url = rtrim( $post_relative_url, '/' );
// With no valid value the filter would find all existing redirections
if ( $post_relative_url == '') { return new WP_Error( 'programmable_redirection_redirects', "\$post_relative_url is empty in " . __METHOD__ ); }
$redir_filter_args = array(
'direction' => 'asc',
'per_page' => '200',
'filterBy' => [
'match' => 'url',
'url-match' => 'plain',
'url' => $post_relative_url,
'action' => 'url',
],
);
$redir_filter_result = Red_Item::get_filtered( $redir_filter_args );
$redir_items = $redir_filter_result['items'];
foreach ( $redir_items as $key => $redir_item ) {
// Check for precise match of URL as the filter uses SQL 'LIKE' for compare
if ( strcmp($post_relative_url, $redir_item['match_url']) != 0 ) {
unset( $redir_items[$key] );
}
}
return array_values( $redir_items ); // reindex the array
}
/**
* Create a new redirection entry using the PHP API of plugin Redirection.
* Store the new entry in the group given as parameter.
*
* @param WP_Post $post The post object or ID.
* @param string $redirect_to_link New link path with query params (same host name may be omitted).
* @param int $redir_status_code HTTP status code to be returned with the redirect.
* @param string $redir_group_name Name of the group where the redirect will be listed in the group admin page of plugin Redirection.
*
* @return $redir_item Array|WP_Error An array with the properties of a newly created redirect or WP_Error in case of failure.
*
* @since 1.0.1
*/
public static function create_redirection_redirect( $post, $redirect_to_link, $redir_status_code, $redir_group_name = '' ) {
// Check if API of plugin Redirection is available!
if ( !method_exists( 'Red_Group', 'get_all') ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Group::get_all()' of plugin Redirector does not exist in " . __METHOD__ ); }
if ( !method_exists( 'Red_Item' , 'create' ) ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Item::create()' of plugin Redirector does not exist in " . __METHOD__ ); }
$redir_group_id = 0;
if ($redir_group_name != '') {
$group_filter = array(
'filterBy' => [
'name' => $redir_group_name,
],
);
$redir_groups = Red_Group::get_all( $group_filter );
if ( count($redir_groups) > 0 ) {
$redir_group_id = $redir_groups[0]['id'];
}
}
$post_relative_url = str_replace( home_url(), '', get_permalink( $post ) );
$redir_args = array(
//'status' => 'enabled', // default: enabled
//'position' => ??, // defines the order how matching redirections are evaluated, added after the last position in the group
'match_data' => [
'source' => [
//'flag_regex' => false, // default: false
'flag_query' => 'ignore', // default: 'exact'
//'flag_case' => false, // default: false
'flag_trailing' => true, // default: false
],
//'options' => [
// 'log_exclude' => false, // default: false
//],
],
//'regex' => false, // default: false
'url' => $post_relative_url,
'match_type' => 'url',
'group_id' => $redir_group_id,
'action_type' => 'url',
'action_code' => $redir_status_code,
'action_data' => [ // available keys can be found as the name of classes in folder /redirection/matches which implement abtract class /redirection/models/match.php
'url' => $redirect_to_link,
],
);
$redir_item = Red_Item::create( $redir_args );
return $redir_item;
}
/**
* Remove all redirection entries for a post using the PHP API of plugin Redirection.
*
* @param WP_Post $post The post object or ID.
*
* @return void
*
* @since 1.0.1
*/
public static function remove_redirection_redirect( $post ) {
// Check if API of plugin Redirection is available!
if ( !method_exists( 'Red_Item' , 'get_by_id' ) ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Item::get_by_id()' of plugin Redirector does not exist in " . __METHOD__ ); }
if ( !method_exists( 'Red_Item' , 'delete' ) ) { return new WP_Error( 'programmable_redirection_redirects', "Method 'Red_Item::delete()' of plugin Redirector does not exist in " . __METHOD__ ); }
$redir_items = self::get_redirection_redirects( $post );
if ( $redir_items instanceof WP_Error ) { return $redir_items; }
if ( empty($redir_items) || !is_array($redir_items) ) { return; }
foreach ( $redir_items as $redir_item) {
$redir_obj = Red_Item::get_by_id( $redir_item['id'] );
if ( $redir_obj instanceof Red_Item) {
$redir_obj->delete();
}
}
}
}
@stilografico
Copy link

I found your class on [this topic(https://wordpress.org/support/topic/add-remove-redirects-programmatically/)], very useful, thank you!

I added some conditional code at line 149 for non published posts. Not perfect but working in my case.
Since my aim was to programmatically create redirects for posts changed from publish to draft, the "get_permalink" function gives back the non-pretty permalink, which is useless to properly handle an obsolete URL. The solution is to read the "post_name" and the slug base to reconstruct the old permalink.

// Get the archive link for the custom post type
$archive_link = get_post_type_archive_link($post->post_type);
// Extract the post slug base from the archive link
$post_slug_base = '';
if ($archive_link) {
$parsed_url = parse_url($archive_link);
if (isset($parsed_url['path'])) {
$path_parts = explode('/', trim($parsed_url['path'], '/'));
$post_slug_base = end($path_parts);
}
}
// Handle WooCommerce products slug base
if ($post->post_type == 'product') {
$post_slug_base = '';
$product_permalink_structure = get_option('woocommerce_permalinks');
if ($product_permalink_structure) {
$post_slug_base = $product_permalink_structure['product_base'];
}
}
$post_relative_url = ($post->post_status == 'publish') ? str_replace(home_url(), '', get_permalink($post)) : $post_slug_base . '/' . $post->post_name;

@jotazzu
Copy link
Author

jotazzu commented Jan 26, 2024

Hi @stilografico,
I had the problem too. When hooking into the status change actions it's to late, the post is already saved and function get_permalink() fails. As I use this class in a cron run, I simply can create the redirect before the changes are saved.

So, if you want to create a redirect when the update button in the post edit screen is clicked I recommend to use the action hook 'pre_post_update'. At this point the post is still published and get_permalink() should return proper results.

@stilografico
Copy link

@jotazzu thank you!
Nice solution, I'm going to try it, seems definitely more reliable than using post_name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment