This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,775 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Additional hooks for "Permalink Manager Pro"
*/
class Permalink_Manager_Actions {
public function __construct() {
add_action( 'admin_init', array( $this, 'trigger_action' ), 9 );
add_action( 'admin_init', array( $this, 'extra_actions' ) );
// Ajax-based functions
if ( is_admin() ) {
add_action( 'wp_ajax_pm_bulk_tools', array( $this, 'ajax_bulk_tools' ) );
add_action( 'wp_ajax_pm_save_permalink', array( $this, 'ajax_save_permalink' ) );
add_action( 'wp_ajax_pm_detect_duplicates', array( $this, 'ajax_detect_duplicates' ) );
add_action( 'wp_ajax_pm_dismissed_notice_handler', array( $this, 'ajax_hide_global_notice' ) );
}
}
/**
* Route the requests to functions that save datasets with associated callbacks
*/
public function trigger_action() {
global $permalink_manager_after_sections_html;
// 1. Check if the form was submitted
if ( empty( $_POST ) ) {
return;
}
// 2. Do nothing if search query is not empty
if ( isset( $_POST['uri_editor_nonce'] ) && ( isset( $_REQUEST['search-submit'] ) || isset( $_REQUEST['filter-button'] ) ) ) {
$this->trigger_filter_action();
}
$actions_map = array(
'uri_editor_nonce' => array( 'function' => 'update_all_permalinks', 'display_uri_table' => true ),
'permalink_manager_options' => array( 'function' => 'save_settings' ),
'permalink_manager_permastructs' => array( 'function' => 'save_permastructures' ),
'import' => array( 'function' => 'import_custom_permalinks_uris' ),
);
// 3. Find the action
foreach ( $actions_map as $action => $map ) {
$nonce = ( isset( $_POST[ $action ] ) ) ? sanitize_key( $_POST[ $action ] ) : '';
if ( ! empty( $nonce ) && wp_verify_nonce( $nonce, 'permalink-manager' ) ) {
// Execute the function
$output = call_user_func( array( $this, $map['function'] ) );
// Get list of updated URIs
if ( ! empty( $map['display_uri_table'] ) ) {
$updated_slugs_count = ( isset( $output['updated_count'] ) && $output['updated_count'] > 0 ) ? $output['updated_count'] : false;
$updated_slugs_array = ( $updated_slugs_count ) ? $output['updated'] : '';
}
// Trigger only one function
break;
}
}
// 4. Display the slugs table (and append the globals)
if ( isset( $updated_slugs_count ) && isset( $updated_slugs_array ) ) {
$permalink_manager_after_sections_html .= Permalink_Manager_UI_Elements::display_updated_slugs( $updated_slugs_array );
}
}
/**
* Adjust the Bulk URI Editor filter URL
*
* @return void
*/
public function trigger_filter_action() {
if ( empty( $_POST['uri_editor_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['uri_editor_nonce'] ), 'permalink-manager' ) ) {
return;
}
$query_args = array(
'langcode' => ! empty( $_REQUEST['langcode'] ) ? sanitize_key( $_REQUEST['langcode'] ) : null,
'month' => ! empty( $_REQUEST['month'] ) ? sanitize_key( $_REQUEST['month'] ) : null,
's' => ! empty( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : null,
);
if ( ! empty( $query_args ) ) {
$sendback = remove_query_arg( array_keys( $query_args ), wp_get_referer() );
$sendback = add_query_arg( array_filter( $query_args ), $sendback );
wp_safe_redirect( $sendback );
exit;
}
}
/**
* Route the requests to the additional tools-related functions with the relevant callbacks
*/
public static function extra_actions() {
global $permalink_manager_before_sections_html;
if ( current_user_can( 'manage_options' ) && ! empty( $_GET['permalink-manager-nonce'] ) ) {
// Check if the nonce field is correct
$nonce = sanitize_key( $_GET['permalink-manager-nonce'] );
if ( ! wp_verify_nonce( $nonce, 'permalink-manager' ) ) {
$permalink_manager_before_sections_html = Permalink_Manager_UI_Elements::get_alert_message( __( 'You are not allowed to remove Permalink Manager data!', 'permalink-manager' ), 'error updated_slugs' );
return;
}
if ( isset( $_GET['clear-permalink-manager-uris'] ) ) {
self::clear_all_uris();
} else if ( isset( $_GET['remove-permalink-manager-settings'] ) ) {
$option_name = sanitize_key( $_GET['remove-permalink-manager-settings'] );
self::remove_plugin_data( $option_name );
} else if ( ! empty( $_REQUEST['remove-uri'] ) ) {
$uri_key = sanitize_key( $_REQUEST['remove-uri'] );
self::force_clear_single_element_uris_and_redirects( $uri_key );
} else if ( ! empty( $_REQUEST['remove-redirect'] ) ) {
$redirect_key = sanitize_key( $_REQUEST['remove-redirect'] );
self::force_clear_single_redirect( $redirect_key );
}
} else if ( ! empty( $_POST['screen-options-apply'] ) ) {
self::save_screen_options();
}
}
/**
* Bulk remove obsolete custom permalinks and redirects
*/
public static function clear_all_uris() {
global $permalink_manager_redirects, $permalink_manager_before_sections_html;
// Get all custom permalinks & redirects
$custom_permalinks = Permalink_Manager_URI_Functions::get_all_uris();
$custom_redirects = (array) $permalink_manager_redirects;
// Check if array with custom URIs exists
if ( empty( $custom_permalinks ) ) {
return;
}
// Count removed URIs & redirects
$removed_uris = 0;
$removed_redirects = 0;
// Get all element IDs
$element_ids = array_merge( array_keys( $custom_permalinks ), array_keys( $custom_redirects ) );
// 1. Remove unused custom URI & redirects for deleted post or term
foreach ( $element_ids as $element_id ) {
$count = self::clear_single_element_uris_and_redirects( $element_id, true );
$removed_uris = ( ! empty( $count[0] ) ) ? $count[0] + $removed_uris : $removed_uris;
$removed_redirects = ( ! empty( $count[1] ) ) ? $count[1] + $removed_redirects : $removed_redirects;
}
// 2. Keep only a single redirect
$removed_redirects += self::clear_redirects_array();
// 3. Save cleared URIs & Redirects
if ( $removed_uris > 0 || $removed_redirects > 0 ) {
Permalink_Manager_URI_Functions::save_all_uris();
update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
// Translators: 1: Number of custom URIs, 2: Number of custom redirects.
$permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( sprintf( __( '%1$d Custom URIs and %2$d Custom Redirects were removed!', 'permalink-manager' ), $removed_uris, $removed_redirects ), 'updated updated_slugs' );
} else {
$permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( __( 'No Custom URIs or Custom Redirects were removed!', 'permalink-manager' ), 'error updated_slugs' );
}
}
/**
* Remove obsolete custom permalink & redirects for specific post or term
*
* @param string|int $element_id
* @param bool $count_removed
*
* @return array
*/
public static function clear_single_element_uris_and_redirects( $element_id, $count_removed = false ) {
global $wpdb, $permalink_manager_redirects, $permalink_manager_options;
// Count removed URIs & redirects
$removed_uris = 0;
$removed_redirects = 0;
// Only admin users can remove the broken URIs for removed post types & taxonomies
$check_if_admin = is_admin();
// Check if the advanced mode is turned on
$advanced_mode = Permalink_Manager_Helper_Functions::is_advanced_mode_on();
// If "Disable URI Editor to disallow Permalink changes" is set globally, the pages that follow the global settings should also be removed
if ( $advanced_mode && ! empty( $permalink_manager_options["general"]["auto_update_uris"] ) && $permalink_manager_options["general"]["auto_update_uris"] == 2 ) {
$strict_mode = true;
} else {
$strict_mode = false;
}
// 1. Check if element exists
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct SQL query is needed to join multiple tables
if ( strpos( $element_id, 'tax-' ) !== false ) {
$term_id = preg_replace( "/[^0-9]/", "", $element_id );
$term_info = $wpdb->get_row( $wpdb->prepare( "SELECT taxonomy, meta_value FROM {$wpdb->term_taxonomy} AS t LEFT JOIN {$wpdb->termmeta} AS tm ON tm.term_id = t.term_id AND tm.meta_key = 'auto_update_uri' WHERE t.term_id = %d", $term_id ) );
// Custom URIs for disabled taxonomies may only be deleted via the admin dashboard, although they will always be removed if the term no longer exists in the database
$remove = ( ! empty( $term_info->taxonomy ) ) ? Permalink_Manager_Helper_Functions::is_taxonomy_disabled( $term_info->taxonomy, $check_if_admin ) : true;
// Remove custom URIs for URIs disabled in URI Editor
if ( $strict_mode ) {
$remove = ( empty( $term_info->meta_value ) || $term_info->meta_value == 2 ) ? true : $remove;
} else {
$remove = ( ! empty( $term_info->meta_value ) && $term_info->meta_value == 2 ) ? true : $remove;
}
} else if ( is_numeric( $element_id ) ) {
$post_info = $wpdb->get_row( $wpdb->prepare( "SELECT post_type, meta_value FROM {$wpdb->posts} AS p LEFT JOIN {$wpdb->postmeta} AS pm ON pm.post_ID = p.ID AND pm.meta_key = 'auto_update_uri' WHERE ID = %d AND post_status NOT IN ('auto-draft', 'trash') AND post_type != 'nav_menu_item'", $element_id ) );
// Custom URIs for disabled post types may only be deleted via the admin dashboard, although they will always be removed if the post no longer exists in the database
$remove = ( ! empty( $post_info->post_type ) ) ? Permalink_Manager_Helper_Functions::is_post_type_disabled( $post_info->post_type, $check_if_admin ) : true;
// Remove custom URIs for URIs disabled in URI Editor
if ( $strict_mode ) {
$remove = ( empty( $post_info->meta_value ) || $post_info->meta_value == 2 ) ? true : $remove;
} else {
$remove = ( ! empty( $post_info->meta_value ) && $post_info->meta_value == 2 ) ? true : $remove;
}
// Remove custom URIs for attachments redirected with Yoast's SEO Premium
$yoast_permalink_options = ( class_exists( 'WPSEO_Premium' ) ) ? get_option( 'wpseo_permalinks' ) : array();
if ( ! empty( $yoast_permalink_options['redirectattachment'] ) && $post_info->post_type == 'attachment' ) {
$attachment_parent = $wpdb->get_var( $wpdb->prepare( "SELECT post_parent FROM {$wpdb->prefix}posts WHERE ID = %d AND post_type = %s", $element_id, 'attachment' ) );
if ( ! empty( $attachment_parent ) ) {
$remove = true;
}
}
}
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
// 2A. Remove ALL unused custom permalinks & redirects
if ( ! empty( $remove ) ) {
$current_uri = Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null );
// Remove URI
if ( ! empty( $current_uri ) ) {
$removed_uris = 1;
Permalink_Manager_URI_Functions::remove_single_uri( $element_id, null, false );
}
// Remove all custom redirects
if ( ! empty( $permalink_manager_redirects[ $element_id ] ) && is_array( $permalink_manager_redirects[ $element_id ] ) ) {
$removed_redirects = count( $permalink_manager_redirects[ $element_id ] );
unset( $permalink_manager_redirects[ $element_id ] );
}
} // 2B. Check if the post/term uses the same URI for both permalink & custom redirects
else {
$removed_redirect = self::clear_single_element_duplicated_redirect( $element_id, false );
$removed_redirects = ( ! empty( $removed_redirect ) ) ? 1 : 0;
}
// Check if function should only return the counts or update
if ( $count_removed ) {
return array( $removed_uris, $removed_redirects );
} else if ( ! empty( $removed_uris ) || ! empty( $removed_redirects ) ) {
Permalink_Manager_URI_Functions::save_all_uris();
update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
}
return array();
}
/**
* Remove the duplicated custom redirect if the post/term has the same URI for both custom permalink and custom redirect
*
* @param string|int $element_id
* @param bool $save_redirects
* @param string $uri
*
* @return int
*/
public static function clear_single_element_duplicated_redirect( $element_id, $save_redirects = true, $uri = null ) {
global $permalink_manager_redirects;
// If the custom permalink is not changed ($uri) use the one that is currently used
if ( ! empty( $uri ) ) {
$current_uri = $uri;
} else {
$current_uri = Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null );
}
if ( ! empty( $current_uri ) && ! empty( $permalink_manager_redirects[ $element_id ] ) && in_array( $current_uri, $permalink_manager_redirects[ $element_id ] ) ) {
$duplicated_redirect_id = array_search( $current_uri, $permalink_manager_redirects[ $element_id ] );
unset( $permalink_manager_redirects[ $element_id ][ $duplicated_redirect_id ] );
}
// Update the redirects array in the database if the duplicated redirect was unset
if ( isset( $duplicated_redirect_id ) && $save_redirects ) {
update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
}
return ( isset( $duplicated_redirect_id ) ) ? 1 : 0;
}
/**
* Remove the duplicated if the same URI is used for multiple custom redirects and return the removed redirects count
*
* @param bool $save_redirects
*
* @return int
*/
public static function clear_redirects_array( $save_redirects = false ) {
global $permalink_manager_redirects;
$removed_redirects = 0;
$all_redirect_duplicates = Permalink_Manager_Admin_Functions::get_all_duplicates();
foreach ( $all_redirect_duplicates as $single_redirect_duplicate ) {
$last_element = reset( $single_redirect_duplicate );
foreach ( $single_redirect_duplicate as $redirect_key ) {
// Keep a single redirect
if ( $last_element == $redirect_key ) {
continue;
}
preg_match( "/redirect-(\d+)_(tax-\d+|\d+)/", $redirect_key, $ids );
if ( ! empty( $ids[2] ) && ! empty( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] ) ) {
$removed_redirects ++;
unset( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] );
}
}
}
// Update the redirects array in the database if the duplicated redirect was unset
if ( isset( $duplicated_redirect_id ) && $save_redirects ) {
update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
}
return $removed_redirects;
}
/**
* Remove custom permalinks & custom redirects for requested post or term
*
* @param $uri_key
*
* @return bool
*/
public static function force_clear_single_element_uris_and_redirects( $uri_key ) {
global $permalink_manager_redirects, $permalink_manager_before_sections_html;
$custom_uri = Permalink_Manager_URI_Functions::get_single_uri( $uri_key, false, true, null );
// Check if custom URI is set
if ( ! empty( $custom_uri ) ) {
Permalink_Manager_URI_Functions::remove_single_uri( $uri_key, null, true );
// translators: %s is the custom URI that was removed.
$updated = Permalink_Manager_UI_Elements::get_alert_message( sprintf( __( 'URI "%s" was removed successfully!', 'permalink-manager' ), $custom_uri ), 'updated' );
}
// Check if custom redirects are set
if ( isset( $permalink_manager_redirects[ $uri_key ] ) ) {
unset( $permalink_manager_redirects[ $uri_key ] );
update_option( 'permalink-manager-redirects', $permalink_manager_redirects );
}
if ( empty( $updated ) ) {
$permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( __( 'URI and/or custom redirects does not exist or were already removed!', 'permalink-manager' ), 'error' );
} else {
// Display the alert in admin panel
if ( isset( $permalink_manager_before_sections_html ) && is_admin() ) {
$permalink_manager_before_sections_html .= $updated;
}
}
return true;
}
/**
* Remove only custom redirects for requested post or term
*
* @param string $redirect_key
*/
public static function force_clear_single_redirect( $redirect_key ) {
global $permalink_manager_redirects, $permalink_manager_before_sections_html;
preg_match( "/redirect-(\d+)_(tax-\d+|\d+)/", $redirect_key, $ids );
if ( ! empty( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] ) ) {
unset( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] );
update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
$permalink_manager_before_sections_html = Permalink_Manager_UI_Elements::get_alert_message( __( 'The redirect was removed successfully!', 'permalink-manager' ), 'updated' );
}
}
/**
* Save "Screen Options"
*/
public static function save_screen_options() {
check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
$screen_options = ( isset( $_POST['screen-options'] ) ) ? map_deep( wp_unslash( $_POST['screen-options'] ), 'sanitize_text_field' ) : array();
if ( ! empty( $screen_options ) ) {
self::save_settings( 'screen-options', $screen_options );
}
}
/**
* Save the plugin settings
*
* @param bool $field
* @param bool $value
* @param bool $display_alert
*/
public static function save_settings( $field = false, $value = false, $display_alert = true ) {
global $permalink_manager_options, $permalink_manager_before_sections_html;
// Info: The settings array is used also by "Screen Options"
$new_options = $permalink_manager_options;
// Save only selected field/sections
if ( $field && $value ) {
$new_options[ $field ] = $value;
} else {
$post_fields = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated in enclosing function
foreach ( $post_fields as $option_name => $option_value ) {
$new_options[ $option_name ] = $option_value;
}
}
// Allow only white-listed option groups
foreach ( $new_options as $group => $group_options ) {
if ( ! in_array( $group, array( 'licence', 'screen-options', 'general', 'permastructure-settings', 'stop-words' ) ) ) {
unset( $new_options[ $group ] );
}
}
// Sanitize & override the global with new settings
$new_options = Permalink_Manager_Helper_Functions::sanitize_array( $new_options );
$permalink_manager_options = $new_options = array_filter( $new_options );
// Save the settings in database
update_option( 'permalink-manager', $new_options );
// Display the message
$permalink_manager_before_sections_html .= ( $display_alert ) ? Permalink_Manager_UI_Elements::get_alert_message( __( 'The settings are saved!', 'permalink-manager' ), 'updated' ) : "";
}
/**
* Save the permastructures
*/
public static function save_permastructures() {
global $permalink_manager_permastructs;
$permastructure_options = $permastructures = array();
$permastructure_types = array( 'post_types', 'taxonomies' );
// Split permastructures & sanitize them
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- The nonce is already validated in enclosing function
foreach ( $permastructure_types as $type ) {
if ( empty( $_POST[ $type ] ) || ! is_array( $_POST[ $type ] ) ) {
continue;
}
$permastructures[ $type ] = $_POST[ $type ];
foreach ( $permastructures[ $type ] as &$single_permastructure ) {
$single_permastructure = Permalink_Manager_Helper_Functions::sanitize_title( $single_permastructure, true, false, false );
$single_permastructure = trim( $single_permastructure, '\/ ' );
}
}
if ( ! empty( $_POST['permastructure-settings'] ) ) {
$permastructure_options = $_POST['permastructure-settings'];
}
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
// A. Permastructures
if ( ! empty( $permastructures['post_types'] ) || ! empty( $permastructures['taxonomies'] ) ) {
// Override the global with settings
$permalink_manager_permastructs = $permastructures;
// Save the settings in database
update_option( 'permalink-manager-permastructs', $permastructures );
}
// B. Permastructure settings
if ( ! empty( $permastructure_options ) ) {
self::save_settings( 'permastructure-settings', $permastructure_options );
}
}
/**
* Update all permalinks in "Bulk URI Editor"
*/
function update_all_permalinks() {
// Check if posts or terms should be updated
if ( ! empty( $_POST['content_type'] ) && $_POST['content_type'] == 'taxonomies' ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated in enclosing function
return Permalink_Manager_URI_Functions_Tax::update_all_permalinks();
} else {
return Permalink_Manager_URI_Functions_Post::update_all_permalinks();
}
}
/**
* Remove a specific section of the plugin data stored in the database
*
* @param $field_name
*/
public static function remove_plugin_data( $field_name ) {
global $permalink_manager, $permalink_manager_before_sections_html;
// Make sure that the user is allowed to remove the plugin data
if ( ! current_user_can( 'manage_options' ) ) {
$permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( __( 'You are not allowed to remove Permalink Manager data!', 'permalink-manager' ), 'error updated_slugs' );
}
switch ( $field_name ) {
case 'uris' :
$option_name = 'permalink-manager-uris';
$alert = __( 'Custom permalinks', 'permalink-manager' );
break;
case 'redirects' :
$option_name = 'permalink-manager-redirects';
$alert = __( 'Custom redirects', 'permalink-manager' );
break;
case 'external-redirects' :
$option_name = 'permalink-manager-external-redirects';
$alert = __( 'External redirects', 'permalink-manager' );
break;
case 'permastructs' :
$option_name = 'permalink-manager-permastructs';
$alert = __( 'Permastructure settings', 'permalink-manager' );
break;
case 'settings' :
$option_name = 'permalink-manager';
$alert = __( 'Permastructure settings', 'permalink-manager' );
break;
default :
$alert = '';
}
if ( ! empty( $option_name ) ) {
// Remove the option from DB
delete_option( $option_name );
// Reload globals
$permalink_manager->get_options_and_globals();
// Translators: %s is the name of the data field that was removed
$alert_message = sprintf( __( '%s were removed!', 'permalink-manager' ), $alert );
$permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( $alert_message, 'updated updated_slugs' );
}
}
/**
* Trigger bulk tools ("Regenerate & reset", "Find & replace") via AJAX
*/
function ajax_bulk_tools() {
global $sitepress, $wpdb;
// Define variables
$return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No items</strong> were processed!', 'permalink-manager' ), 'error updated_slugs' ) );
// Get the name of the function
if ( isset( $_POST['regenerate'] ) ) {
$nonce_name = sanitize_key( $_POST['regenerate'] );
$operation = 'regenerate';
} else if ( isset( $_POST['find_and_replace'] ) ) {
$nonce_name = sanitize_key( $_POST['find_and_replace'] );
$operation = ( ! empty( $_POST['old_string'] ) && ! empty( $_POST['new_string'] ) ) ? 'find_and_replace' : '';
}
// Validate the nonce
if ( empty( $nonce_name ) || ! wp_verify_nonce( $nonce_name, 'permalink-manager' ) ) {
$error = true;
$return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( 'Nonce is invalid!', 'permalink-manager' ), 'error updated_slugs' ) );
}
// Get the session ID
$uniq_id = ( ! empty( $_POST['pm_session_id'] ) ) ? sanitize_key( $_POST['pm_session_id'] ) : '';
// Get content type & post statuses
if ( ! empty( $_POST['content_type'] ) && $_POST['content_type'] == 'taxonomies' ) {
$content_type = 'taxonomies';
if ( empty( $_POST['taxonomies'] ) ) {
$error = true;
$return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No taxonomy</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
}
} else {
$content_type = 'post_types';
// Check if any post type was selected
if ( empty( $_POST['post_types'] ) ) {
$error = true;
$return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No post type</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
}
// Check post status
if ( empty( $_POST['post_statuses'] ) ) {
$error = true;
$return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No post status</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
}
}
if ( ! empty( $operation ) && empty( $error ) ) {
// Hotfix for WPML (start)
if ( $sitepress ) {
remove_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10 );
remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1 );
remove_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10 );
remove_filter( 'get_pages', array( $sitepress, 'get_pages_adjust_ids' ), 1 );
}
// Get the mode
$mode = ( isset( $_POST['mode'] ) ) ? sanitize_key( $_POST['mode'] ) : 'custom_uris';
$preview_mode = ( ! empty( $_POST['preview_mode'] ) ) ? true : false;
// Get items (try to get them from transient)
$items = get_transient( "pm_{$uniq_id}" );
// Get the iteration count and chunk size
$iteration = isset( $_POST['iteration'] ) ? intval( $_POST['iteration'] ) : 1;
$chunk_size = apply_filters( 'permalink_manager_chunk_size', 50 );
if ( empty( $items ) && ! empty ( $chunk_size ) ) {
if ( $content_type == 'taxonomies' ) {
$items = Permalink_Manager_URI_Functions_Tax::get_items();
} else {
$items = Permalink_Manager_URI_Functions_Post::get_items();
}
if ( ! empty( $items ) ) {
// Count how many items need to be processed
$total = count( $items );
// Split items array into chunks and save them to transient
$items = array_chunk( $items, $chunk_size );
set_transient( "pm_{$uniq_id}", $items, 600 );
// Check for MySQL errors
if ( ! empty( $wpdb->last_error ) ) {
printf( '%s (%sMB)', esc_html( $wpdb->last_error ), esc_html( number_format( strlen( serialize( $items ) ) / 1000000, 2 ) ) );
http_response_code( 500 );
die();
}
}
}
// Get homepage URL and ensure that it ends with slash
$home_url = Permalink_Manager_Permastructure_Functions::get_permalink_base() . "/";
// Process the variables from $_POST object
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- The escaped slashes can be a part of REGEX formula
$old_string = ( ! empty( $_POST['old_string'] ) ) ? str_replace( $home_url, '', esc_sql( sanitize_text_field( $_POST['old_string'] ) ) ) : '';
$new_string = ( ! empty( $_POST['new_string'] ) ) ? str_replace( $home_url, '', esc_sql( sanitize_text_field( $_POST['new_string'] ) ) ) : '';
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
// Process only one subarray
if ( ! empty( $items[ $iteration - 1 ] ) ) {
$chunk = $items[ $iteration - 1 ];
// Check how many iterations are needed
$total_iterations = count( $items );
if ( $content_type == 'taxonomies' ) {
$output = Permalink_Manager_URI_Functions_Tax::bulk_process_items( $chunk, $mode, $operation, $old_string, $new_string, $preview_mode );
} else {
$output = Permalink_Manager_URI_Functions_Post::bulk_process_items( $chunk, $mode, $operation, $old_string, $new_string, $preview_mode );
}
if ( ! empty( $output['updated_count'] ) ) {
$return = array_merge( $return, (array) Permalink_Manager_UI_Elements::display_updated_slugs( $output['updated'], true, true, $preview_mode ) );
$return['updated_count'] = $output['updated_count'];
}
// Send total number of processed items with a first chunk
if ( ! empty( $total ) && ! empty( $total_iterations ) && $iteration == 1 ) {
$return['total'] = $total;
$return['items'] = $items;
}
$return['iteration'] = $iteration;
$return['total_iterations'] = $total_iterations;
$return['progress'] = $chunk_size * $iteration;
$return['chunk'] = $chunk;
// After all chunks are processed remove the transient
if ( $iteration == $total_iterations ) {
delete_transient( "pm_{$uniq_id}" );
}
}
// Hotfix for WPML (end)
if ( $sitepress ) {
add_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10, 4 );
add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
add_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10, 2 );
add_filter( 'get_pages', array( $sitepress, 'get_pages_adjust_ids' ), 1, 2 );
}
}
wp_send_json( $return );
die();
}
/**
* Save permalink via AJAX
*/
public function ajax_save_permalink() {
$element_id = ( ! empty( $_POST['permalink-manager-edit-uri-element-id'] ) ) ? sanitize_key( $_POST['permalink-manager-edit-uri-element-id'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is validated inside update_post_hook
if ( ! empty( $element_id ) && is_numeric( $element_id ) && current_user_can( 'edit_post', $element_id ) ) {
Permalink_Manager_URI_Functions_Post::update_post_hook( $element_id );
// Reload URI Editor & clean post cache
clean_post_cache( $element_id );
die();
}
}
/**
* Check if URI was used before
*/
function ajax_detect_duplicates() {
$duplicate_alert = __( "Permalink is already in use, please select another one!", "permalink-manager" );
$duplicates_data = array();
$custom_uris = ( ! empty( $_REQUEST['custom_uris'] ) ) ? Permalink_Manager_Helper_Functions::sanitize_array( $_REQUEST['custom_uris'] ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- No data is saved here
if ( ! empty( $custom_uris ) ) {
// Check each URI
foreach ( $custom_uris as $raw_element_id => $element_uri ) {
$element_id = sanitize_key( $raw_element_id );
$duplicates_data[ $element_id ] = Permalink_Manager_URI_Functions::is_uri_duplicated( $element_uri, $element_id ) ? $duplicate_alert : 0;
}
} else if ( ! empty( $_REQUEST['custom_uri'] ) && ! empty( $_REQUEST['element_id'] ) ) {
$duplicates_data = Permalink_Manager_URI_Functions::is_uri_duplicated( $_REQUEST['custom_uri'], sanitize_key( $_REQUEST['element_id'] ) );
}
wp_send_json( $duplicates_data );
}
/**
* Hide global notices (AJAX)
*/
function ajax_hide_global_notice() {
global $permalink_manager_alerts;
// Get the ID of the alert
$alert_id = ( ! empty( $_REQUEST['alert_id'] ) ) ? sanitize_title( $_REQUEST['alert_id'] ) : "";
if ( ! empty( $permalink_manager_alerts[ $alert_id ] ) ) {
$dismissed_transient_name = sprintf( 'permalink-manager-notice_%s', $alert_id );
$dismissed_time = ( ! empty( $permalink_manager_alerts[ $alert_id ]['dismissed_time'] ) ) ? (int) $permalink_manager_alerts[ $alert_id ]['dismissed_time'] : DAY_IN_SECONDS;
set_transient( $dismissed_transient_name, 1, $dismissed_time );
}
}
/**
* Import old URIs from "Custom Permalinks" (Pro)
*/
function import_custom_permalinks_uris() {
Permalink_Manager_Third_Parties::import_custom_permalinks_uris();
}
}
@@ -0,0 +1,329 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Additional functions related to WordPress Admin Dashboard UI
*/
class Permalink_Manager_Admin_Functions {
public $sections, $active_section, $active_subsection;
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_menu_page' ) );
add_action( 'admin_init', array( $this, 'init' ) );
add_action( 'admin_bar_menu', array( $this, 'fix_customize_url' ), 41 );
add_action( 'admin_notices', array( $this, 'display_plugin_notices' ) );
add_action( 'admin_notices', array( $this, 'display_global_notices' ) );
}
/**
* Hooks that should be triggered with "admin_init"
*/
public function init() {
// Additional links in "Plugins" page
add_filter( "plugin_action_links_" . PERMALINK_MANAGER_BASENAME, array( $this, "plugins_page_links" ) );
add_filter( "plugin_row_meta", array( $this, "plugins_page_meta" ), 10, 2 );
// Detect current section
$this->sections = apply_filters( 'permalink_manager_sections', array() );
$this->get_current_section();
}
/**
* Use the native URL for "Customize" button in the admin bar
*
* @param WP_Admin_Bar $wp_admin_bar
*/
public function fix_customize_url( $wp_admin_bar ) {
global $permalink_manager_ignore_permalink_filters;
$object = get_queried_object();
$customize = $wp_admin_bar->get_node( 'customize' );
if ( empty( $customize->href ) ) {
return;
}
$permalink_manager_ignore_permalink_filters = true;
if ( ! empty( $object->ID ) && is_a( $object, 'WP_Post' ) ) {
$new_url = get_permalink( $object->ID );
} else if ( ! empty( $object->taxonomy ) && is_a( $object, 'WP_Term' ) ) {
$new_url = get_term_link( $object, $object->taxonomy );
}
$permalink_manager_ignore_permalink_filters = false;
if ( ! empty( $new_url ) ) {
// The original permalink should be already encoded via "utf8_uri_encode()" in "sanitize_title_with_dashes()" function, so there is no need to encode them once again
$new_url = filter_var( $new_url, FILTER_SANITIZE_URL );
$customize_url = preg_replace( '/url=([^&]+)/', "url={$new_url}", $customize->href );
$wp_admin_bar->add_node( array(
'id' => 'customize',
'href' => $customize_url,
) );
}
}
/**
* Get current section of Permalink Manager admin panel
*/
public function get_current_section() {
global $active_section, $active_subsection, $current_admin_tax;
// 1. Get current section
if ( isset( $_GET['page'] ) && $_GET['page'] == PERMALINK_MANAGER_PLUGIN_SLUG ) {
if ( isset( $_POST['section'] ) ) {
$this->active_section = sanitize_title_with_dashes( $_POST['section'] );
} else if ( isset( $_GET['section'] ) ) {
$this->active_section = sanitize_title_with_dashes( $_GET['section'] );
} else {
$sections_names = array_keys( $this->sections );
$this->active_section = $sections_names[0];
}
}
// 2. Get current subsection
if ( $this->active_section && isset( $this->sections[ $this->active_section ]['subsections'] ) ) {
if ( isset( $_POST['subsection'] ) ) {
$this->active_subsection = sanitize_title_with_dashes( $_POST['subsection'] );
} else if ( isset( $_GET['subsection'] ) ) {
$this->active_subsection = sanitize_title_with_dashes( $_GET['subsection'] );
} else {
$subsections_names = array_keys( $this->sections[ $this->active_section ]['subsections'] );
$this->active_subsection = $subsections_names[0];
}
}
// 3. Check if current admin page is related to taxonomies
if ( ! empty( $this->active_subsection ) && substr( $this->active_subsection, 0, 4 ) == 'tax_' ) {
$current_admin_tax = substr( $this->active_subsection, 4, strlen( $this->active_subsection ) );
} else {
$current_admin_tax = false;
}
// Set globals
$active_section = $this->active_section;
$active_subsection = $this->active_subsection;
}
/**
* Add "Tools -> Permalink Manager" to the admin sidebar menu
*/
public function add_menu_page() {
add_management_page( __( 'Permalink Manager', 'permalink-manager' ), __( 'Permalink Manager', 'permalink-manager' ), 'manage_options', PERMALINK_MANAGER_PLUGIN_SLUG, array( $this, 'display_section' ) );
add_action( 'admin_init', array( $this, 'enqueue_cssjs' ) );
}
/**
* Display the plugin sections
*/
public function display_section() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo Permalink_Manager_UI_Elements::get_plugin_sections_html( $this->sections, $this->active_section, $this->active_subsection );
}
/**
* Register the CSS & JS files for the plugin's dashboard
*/
public function enqueue_cssjs() {
wp_enqueue_style( 'permalink-manager-plugins', PERMALINK_MANAGER_URL . '/out/permalink-manager-plugins.css', array(), PERMALINK_MANAGER_VERSION );
wp_enqueue_style( 'permalink-manager', PERMALINK_MANAGER_URL . '/out/permalink-manager-admin.css', array( 'permalink-manager-plugins' ), PERMALINK_MANAGER_VERSION );
wp_enqueue_script( 'permalink-manager-plugins', PERMALINK_MANAGER_URL . '/out/permalink-manager-plugins.js', array( 'jquery', ), PERMALINK_MANAGER_VERSION, array( 'in_footer' => false ) );
wp_enqueue_script( 'permalink-manager', PERMALINK_MANAGER_URL . '/out/permalink-manager-admin.js', array( 'jquery', 'permalink-manager-plugins' ), PERMALINK_MANAGER_VERSION, array( 'in_footer' => false ) );
if ( isset( $_GET['section'] ) && $_GET['section'] === 'permastructs' ) {
wp_enqueue_script( 'thickbox' );
wp_enqueue_style( 'thickbox' );
}
wp_localize_script( 'permalink-manager', 'permalink_manager', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'url' => PERMALINK_MANAGER_URL,
'confirm' => __( 'Are you sure? This action cannot be undone!', 'permalink-manager' ),
'spinners' => admin_url( 'images' )
) );
}
/**
* Get the URL of the plugin's dashboard
*
* @param string $append
*
* @return string
*/
public static function get_admin_url( $append = '' ) {
//return menu_page_url(PERMALINK_MANAGER_PLUGIN_SLUG, false) . $append;
$admin_page = sprintf( "tools.php?page=%s", PERMALINK_MANAGER_PLUGIN_SLUG . $append );
return admin_url( $admin_page );
}
/**
* Add shortcut links for Permalink Manager on "Plugins" page
*
* @param array $links
*
* @return array
*/
public function plugins_page_links( $links ) {
$new_links = array(
sprintf( '<a href="%s">%s</a>', $this->get_admin_url(), __( 'URI Editor', 'permalink-manager' ) ),
sprintf( '<a href="%s">%s</a>', $this->get_admin_url( '&section=settings' ), __( 'Settings', 'permalink-manager' ) ),
);
return array_merge( $links, $new_links );
}
/**
* Add shortcut meta links for Permalink Manager on "Plugins" page
*
* @param array $links
* @param string $file
*
* @return array
*/
public function plugins_page_meta( $links, $file ) {
if ( $file == PERMALINK_MANAGER_BASENAME ) {
$new_links = array(
'doc' => sprintf( '<a href="%s?utm_source=plugin_admin_page" target="_blank">%s</a>', 'https://permalinkmanager.pro/docs/', __( 'Documentation', 'permalink-manager' ) )
);
if ( ! defined( 'PERMALINK_MANAGER_PRO' ) ) {
$new_links['upgrade'] = sprintf( '<a href="%s" target="_blank"><strong>%s</strong></a>', PERMALINK_MANAGER_PROMO, __( 'Buy Permalink Manager Pro', 'permalink-manager' ) );
}
$links = array_merge( $links, $new_links );
}
return $links;
}
/**
* Check if URI Editor should be displayed for current user
*
* @return bool
*/
public static function current_user_can_edit_uris() {
global $permalink_manager_options;
$edit_uris_cap = ( ! empty( $permalink_manager_options['general']['edit_uris_cap'] ) ) ? $permalink_manager_options['general']['edit_uris_cap'] : 'publish_posts';
return current_user_can( $edit_uris_cap );
}
/**
* Display global notices (throughout wp-admin dashboard)
*/
function display_global_notices() {
global $permalink_manager_alerts, $active_section;
$html = "";
if ( ! empty( $permalink_manager_alerts ) && is_array( $permalink_manager_alerts ) ) {
foreach ( $permalink_manager_alerts as $alert_id => $alert ) {
$dismissed_transient_name = sprintf( 'permalink-manager-notice_%s', sanitize_title( $alert_id ) );
$dismissed = get_transient( $dismissed_transient_name );
// Check if alert was dismissed
if ( empty( $dismissed ) ) {
// Hide notice in Permalink Manager Pro
if ( defined( 'PERMALINK_MANAGER_PRO' ) && $alert['show'] == 'pro_hide' ) {
continue;
}
// Display the notice only on the plugin pages
if ( empty( $active_section ) && ! empty( $alert['plugin_only'] ) ) {
continue;
}
// Check if the notice did not expire
if ( isset( $alert['until'] ) && ( time() > strtotime( $alert['until'] ) ) ) {
continue;
}
$html .= Permalink_Manager_UI_Elements::get_alert_message( $alert['txt'], $alert['type'], true, $alert_id );
}
}
}
echo wp_kses_post( $html );
}
/**
* Display notices generated by Permalink Manager tools
*/
function display_plugin_notices() {
global $permalink_manager_before_sections_html;
echo wp_kses_post( $permalink_manager_before_sections_html );
}
/**
* Get the list of all duplicated redirects and custom permalinks
*
* @param bool $include_custom_uris
*
* @return array
*/
public static function get_all_duplicates( $include_custom_uris = true ) {
global $permalink_manager_redirects;
// Make sure that both variables are arrays
$all_uris = ( $include_custom_uris ) ? Permalink_Manager_URI_Functions::get_all_uris() : array();
$permalink_manager_redirects = ( is_array( $permalink_manager_redirects ) ) ? $permalink_manager_redirects : array();
// Convert redirects list, so it can be merged with custom permalinks array
foreach ( $permalink_manager_redirects as $element_id => $redirects ) {
if ( is_array( $redirects ) ) {
foreach ( $redirects as $index => $uri ) {
$all_uris["redirect-{$index}_{$element_id}"] = $uri;
}
}
}
// Count duplicates
$duplicates_groups = array();
$duplicates_list = array_count_values( $all_uris );
$duplicates_list = array_filter( $duplicates_list, function ( $x ) {
return $x >= 2;
} );
// Assign keys to duplicates (group them)
if ( count( $duplicates_list ) > 0 ) {
foreach ( $duplicates_list as $duplicated_uri => $count ) {
$duplicated_ids = array_keys( $all_uris, $duplicated_uri );
// Ignore duplicates in different langauges
if ( Permalink_Manager_URI_Functions::is_uri_duplicated( $duplicated_uri, $duplicated_ids[0], $duplicated_ids ) ) {
$duplicates_groups[ $duplicated_uri ] = $duplicated_ids;
}
}
}
return $duplicates_groups;
}
/**
* Check if Permalink Manager Pro is active
*
* @return bool
*/
public static function is_pro_active() {
if ( defined( 'PERMALINK_MANAGER_PRO' ) && class_exists( 'Permalink_Manager_Pro_License' ) ) {
// Check if license is active
$exp_date = Permalink_Manager_Pro_License::get_expiration_date( true );
$is_pro = ( $exp_date > 2 ) ? false : true;
} else {
$is_pro = false;
}
return $is_pro;
}
}
@@ -0,0 +1,980 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Core functions
*/
class Permalink_Manager_Core_Functions {
public function __construct() {
add_action( 'init', array( $this, 'init_hooks' ), 99 );
}
/**
* Add hooks used by plugin to change the way the permalinks are detected
*
* @return false|void
*/
function init_hooks() {
global $permalink_manager_options;
// Trailing slashes
add_filter( 'permalink_manager_filter_final_term_permalink', array( $this, 'control_trailing_slashes' ), 9 );
add_filter( 'permalink_manager_filter_final_post_permalink', array( $this, 'control_trailing_slashes' ), 9 );
add_filter( 'permalink_manager_filter_post_sample_uri', array( $this, 'control_trailing_slashes' ), 9 );
add_filter( 'wpseo_canonical', array( $this, 'control_trailing_slashes' ), 9 );
add_filter( 'wpseo_opengraph_url', array( $this, 'control_trailing_slashes' ), 9 );
add_filter( 'paginate_links', array( $this, 'control_trailing_slashes' ), 9 );
/**
* Detect & canonical URL/redirect functions
*/
// Do not trigger in back-end
if ( is_admin() && ! wp_doing_ajax() ) {
return false;
}
// Do not trigger if Customizer is loaded
if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
return false;
}
// Use the URIs set in this plugin
add_filter( 'request', array( $this, 'detect_post' ), 0, 1 );
if ( ! is_admin() ) {
// Redirect from old URIs to new URIs + adjust canonical redirect settings
add_action( 'template_redirect', array( $this, 'new_uri_redirect_and_404' ), 1 );
add_action( 'wp', array( $this, 'adjust_canonical_redirect' ), 1 );
// Case-insensitive permalinks
if ( ! empty( $permalink_manager_options['general']['case_insensitive_permalinks'] ) ) {
add_action( 'parse_request', array( $this, 'case_insensitive_permalinks' ), 0 );
}
// Force 404 on non-existing pagination pages
if ( ! empty( $permalink_manager_options['general']['pagination_redirect'] ) ) {
add_action( 'wp', array( $this, 'fix_pagination_pages' ), 0 );
}
}
}
/**
* Change the request array used by WordPress to load specific content item (post, term, archive, etc.)
*
* @param array $query
* @param bool $request_url
* @param bool $return_object
*
* @return array|WP_Post|WP_Term
*/
public static function detect_post( $query, $request_url = false, $return_object = false ) {
global $wp, $wp_rewrite, $permalink_manager_uris, $permalink_manager_options, $pm_query;
// Check if the array with custom URIs is set
if ( ! ( is_array( $permalink_manager_uris ) ) ) {
return $query;
}
// Used in debug mode & endpoints
$old_query = ( ! empty( $request_url ) && empty( $query ) ) ? false : $query;
/**
* 1. Prepare URL and check if it is correct (make sure that both requested URL & home_url share the same protocol and get rid of www prefix)
*/
$request_url = ( ! empty( $request_url ) ) ? parse_url( $request_url, PHP_URL_PATH ) : $_SERVER['REQUEST_URI'];
$request_url = ( ! empty( $request_url ) ) ? strtok( $request_url, "?" ) : $request_url;
// Make sure that either $_SERVER['SERVER_NAME'] or $_SERVER['HTTP_HOST'] are set
if ( empty( $_SERVER['HTTP_HOST'] ) && empty( $_SERVER['SERVER_NAME'] ) ) {
return $query;
}
$http_host = ( ! empty( $_SERVER['HTTP_HOST'] ) ) ? $_SERVER['HTTP_HOST'] : preg_replace( '/www\./i', '', $_SERVER['SERVER_NAME'] );
$request_url = sprintf( "http://%s%s", str_replace( "www.", "", $http_host ), $request_url );
$raw_home_url = trim( get_option( 'home' ) );
$home_url = preg_replace( "/http(s)?:\/\/(www\.)?(.+?)\/?$/", "http://$3", $raw_home_url );
if ( parse_url( $request_url, PHP_URL_HOST ) ) {
// Check if "Deep Detect" is enabled
$deep_detect_enabled = apply_filters( 'permalink_manager_deep_uri_detect', true );
// Sanitize the URL
// $request_url = filter_var($request_url, FILTER_SANITIZE_URL);
// Keep only the URI
$request_url = str_replace( $home_url, "", $request_url );
// Hotfix for language plugins
if ( filter_var( $request_url, FILTER_VALIDATE_URL ) ) {
$request_url = parse_url( $request_url, PHP_URL_PATH );
}
$request_url = trim( $request_url, "/" );
// Get all the endpoints & pattern
$endpoints = Permalink_Manager_Helper_Functions::get_endpoints();
$pattern = "/^(.+?)(?|\/({$endpoints})(?|\/(.*)|$)|\/()([\d]+)\/?)?$/i";
// Use default REGEX to detect post
preg_match( $pattern, $request_url, $regex_parts );
$uri_parts['lang'] = false;
$uri_parts['uri'] = ( ! empty( $regex_parts[1] ) ) ? $regex_parts[1] : "";
$uri_parts['endpoint'] = ( ! empty( $regex_parts[2] ) ) ? $regex_parts[2] : "";
$uri_parts['endpoint_value'] = ( ! empty( $regex_parts[3] ) ) ? $regex_parts[3] : "";
// Allow to filter the results by third-parties + store the URI parts with $pm_query global
$uri_parts = apply_filters( 'permalink_manager_detect_uri', $uri_parts, $request_url, $endpoints );
// Support comment pages
preg_match( "/(.*)\/{$wp_rewrite->comments_pagination_base}-([\d]+)/", $uri_parts['uri'], $regex_parts );
if ( ! empty( $regex_parts[2] ) ) {
$uri_parts['uri'] = $regex_parts[1];
$uri_parts['endpoint'] = 'cpage';
$uri_parts['endpoint_value'] = $regex_parts[2];
}
// Support pagination endpoint
if ( ! empty( $wp_rewrite->pagination_base ) && $uri_parts['endpoint'] == $wp_rewrite->pagination_base ) {
$uri_parts['endpoint'] = 'page';
}
// Stop the function if $uri_parts is empty
if ( empty( $uri_parts ) ) {
return $query;
}
// Store the URI parts in a separate global variable
$pm_query = $uri_parts;
// Get the URI parts from REGEX parts
// $lang = $uri_parts['lang'];
$uri = $uri_parts['uri'];
$endpoint = $uri_parts['endpoint'];
$endpoint_value = $uri_parts['endpoint_value'];
// Trim slashes
$uri = trim( $uri, "/" );
// Ignore URLs with no URI grabbed
if ( empty( $uri ) ) {
return $query;
}
// Check what content type should be loaded in case of duplicate ("posts" or "terms")
$duplicates_priority = apply_filters( 'permalink_manager_duplicates_priority', false );
/**
* 2. Check if the requested URI matches any custom permalink assigned to a post or term
*/
$uri_query_iteration = 1;
$element_object = '';
$excluded_ids = array();
do {
// Store an array with custom permalinks in a separate variable
$all_uris = $permalink_manager_uris;
// Remove empty rows
$all_uris = array_filter( $all_uris );
// In case of multiple elements using the same URI, the function will follow the "permalink_manager_duplicates_priority" filter value to determine whether terms or posts should be ignored
if ( $duplicates_priority ) {
$duplicated_uris = array_keys( $all_uris, $uri );
$duplicates_removed = 0;
$duplicates_count = count( $duplicated_uris );
if ( $duplicates_count > 1 ) {
foreach ( $duplicated_uris as $duplicated_uri_id ) {
if ( ( $duplicates_priority == 'posts' && ! is_numeric( $duplicated_uri_id ) ) || ( $duplicates_priority !== 'posts' && is_numeric( $duplicated_uri_id ) ) ) {
$duplicates_removed ++;
if ( $duplicates_removed < $duplicates_count ) {
$excluded_ids[] = $duplicated_uri_id;
}
}
}
}
}
// If the element was excluded in the previous iteration add it to the array
if ( ! empty( $excluded ) ) {
$excluded_ids[] = $excluded;
}
$excluded = '';
// Exclude all the element detected in the previous iterations
if ( ! empty( $excluded_ids ) ) {
$excluded_ids = array_unique( $excluded_ids );
foreach ( $excluded_ids as $excluded_element ) {
unset( $all_uris[ $excluded_element ] );
}
}
// Flip array for better performance
$all_uris = array_flip( $all_uris );
// Attempt 1.
// Find the element ID
$element_id = isset( $all_uris[ $uri ] ) ? $all_uris[ $uri ] : false;
// Attempt 2.
// Decode both request URI & URIs array & make them lowercase (and save in a separate variable)
if ( empty( $element_id ) ) {
$uri = strtolower( urldecode( $uri ) );
foreach ( $all_uris as $raw_uri => $uri_id ) {
$raw_uri = strtolower( urldecode( $raw_uri ) );
$all_uris[ $raw_uri ] = $uri_id;
}
$element_id = isset( $all_uris[ $uri ] ) ? $all_uris[ $uri ] : $element_id;
}
// Attempt 3.
// Check custom permalinks with endpoints included
if ( $deep_detect_enabled && ! empty( $uri_parts['endpoint_value'] ) ) {
// Check again in case someone used post/tax IDs instead of slugs
if ( empty( $uri_parts['endpoint'] ) && is_numeric( $uri_parts['endpoint_value'] ) ) {
$uri_alt = sprintf( '%s/%s', $uri, $uri_parts['endpoint_value'] );
} // Check again for attachments' custom permalinks
else if ( ! empty( $uri_parts['endpoint'] ) && $uri_parts['endpoint'] == 'attachment' ) {
$uri_alt = sprintf( '%s/%s/%s', $uri, $uri_parts['endpoint'], $uri_parts['endpoint_value'] );
}
if ( ! empty( $uri_alt ) && isset( $all_uris[ $uri_alt ] ) ) {
$element_id = $all_uris[ $uri_alt ];
$endpoint_value = $endpoint = "";
}
}
// Allow to filter the item_id by third-parties after initial detection
$element_id = apply_filters( 'permalink_manager_detected_element_id', $element_id, $uri_parts, $request_url );
// Clear the original query before it is filtered
$query = ( $element_id ) ? array() : $query;
/**
* 2A. Custom URI assigned to taxonomy
*/
if ( strpos( $element_id, 'tax-' ) !== false ) {
// Remove the "tax-" prefix
$term_element_id = intval( preg_replace( "/[^0-9]/", "", $element_id ) );
// Filter detected post ID
$term_element_id = apply_filters( 'permalink_manager_detected_term_id', $term_element_id, $uri_parts, true, $old_query );
// Get the variables to filter wp_query and double-check if taxonomy exists
$term = $element_object = ( ! empty( $term_element_id ) && is_numeric( $term_element_id ) ) ? get_term( $term_element_id ) : false;
$term_taxonomy = ( ! empty( $term->taxonomy ) ) ? $term->taxonomy : false;
$term_taxonomy_object = ( ! empty( $term_taxonomy ) ) ? get_taxonomy( $term_taxonomy ) : '';
// Check if term is allowed
$disabled = ( $term_taxonomy_object && Permalink_Manager_Helper_Functions::is_term_excluded( $term ) ) ? true : false;
// Proceed only if the term is not removed and its taxonomy is not disabled
if ( ! $disabled && $term_taxonomy_object ) {
$term_ancestors = get_ancestors( $element_id, $term_taxonomy );
$final_uri = $term->slug;
// Fix for hierarchical terms
if ( ! empty( $term_ancestors ) ) {
foreach ( $term_ancestors as $parent_id ) {
$parent = get_term( $parent_id, $term_taxonomy );
if ( ! empty( $parent->slug ) ) {
$final_uri = $parent->slug . '/' . $final_uri;
}
}
}
if ( empty( $term_taxonomy_object->query_var ) ) {
$query["taxonomy"] = $term_taxonomy;
$query["term"] = $term->slug;
} else {
$query[ $term_taxonomy_object->query_var ] = $term->slug;
}
} else if ( $disabled ) {
$broken_uri = true;
$query = $old_query;
$excluded = $element_id;
} else {
$query = $old_query;
$excluded = $element_id;
}
} /**
* 2B. Custom URI assigned to post/page/CPT item
*/ else if ( isset( $element_id ) && is_numeric( $element_id ) ) {
// Fix for revisions
$is_revision = wp_is_post_revision( $element_id );
if ( $is_revision ) {
$revision_id = $element_id;
$element_id = $is_revision;
}
// Filter detected post ID
$post_element_id = apply_filters( 'permalink_manager_detected_post_id', $element_id, $uri_parts, false, $old_query );
$post_to_load = $element_object = ( ! empty( $post_element_id ) && is_numeric( $post_element_id ) ) ? get_post( $post_element_id ) : false;
$final_uri = ( ! empty( $post_to_load->post_name ) ) ? $post_to_load->post_name : false;
$post_type = ( ! empty( $post_to_load->post_type ) ) ? $post_to_load->post_type : false;
// Check if post is allowed
$disabled = ( $post_type && Permalink_Manager_Helper_Functions::is_post_excluded( $post_to_load, true ) ) ? true : false;
// Proceed only if the term is not removed and its taxonomy is not disabled
if ( ! $disabled && $post_type ) {
$post_type_object = get_post_type_object( $post_type );
// Fix for hierarchical CPT & pages
if ( ! ( empty( $post_to_load->ancestors ) ) && ! empty( $post_type_object->hierarchical ) ) {
foreach ( $post_to_load->ancestors as $parent ) {
$parent = get_post( $parent );
if ( $parent && $parent->post_name ) {
$final_uri = $parent->post_name . '/' . $final_uri;
}
}
}
// Alter the final query array
if ( $post_to_load->post_status == 'private' && ( ! is_user_logged_in() || current_user_can( 'read_private_posts', $element_id ) !== true ) ) {
$element_id = 0;
$query = $old_query;
} else if ( $post_to_load->post_status == 'draft' || empty( $final_uri ) ) {
// A. The draft permalinks should be allowed for logged-in users
if ( is_user_logged_in() ) {
if ( $post_type == 'page' ) {
$query['page_id'] = $element_id;
} else {
$query['p'] = $element_id;
}
$query['preview'] = true;
$query['post_type'] = $post_type;
} // B. The draft permalinks should be disabled for non-logged-in visitors
else if ( $post_to_load->post_status == 'draft' ) {
$query['pagename'] = '-';
$query['error'] = '404';
$element_id = 0;
} else {
$query = $old_query;
$excluded = $element_id;
}
} else if ( $post_type == 'page' ) {
$query['pagename'] = $final_uri;
// $query['post_type'] = $post_type;
} else if ( $post_type == 'post' ) {
$query['name'] = $final_uri;
} else if ( $post_type == 'attachment' ) {
$query['attachment'] = $final_uri;
} else {
// Get the query var
$query_var = ( ! empty( $post_type_object->query_var ) ) ? $post_type_object->query_var : $post_type;
$query['name'] = $final_uri;
$query['post_type'] = $post_type;
$query[ $query_var ] = $final_uri;
}
} else if ( $disabled ) {
$broken_uri = true;
$query = $old_query;
$excluded = $element_id;
} else {
$query = $old_query;
$excluded = $element_id;
}
}
// Auto-remove removed term custom URI & redirects (works if enabled in plugin settings)
if ( ! empty( $broken_uri ) && ( ! empty( $permalink_manager_options['general']['auto_fix_duplicates'] ) ) && $permalink_manager_options['general']['auto_fix_duplicates'] == 1 ) {
// Do not trigger if WP Rocket cache plugin is turned on
if ( ! defined( 'WP_ROCKET_VERSION' ) && is_array( $permalink_manager_uris ) ) {
$broken_element_id = ( ! empty( $revision_id ) ) ? $revision_id : $element_id;
$remove_broken_uri = ( ! empty( $broken_element_id ) ) ? Permalink_Manager_Actions::force_clear_single_element_uris_and_redirects( $broken_element_id ) : '';
// Reload page if success
if ( $remove_broken_uri && ! headers_sent() ) {
header( "Refresh:0" );
exit();
}
}
}
// Overwrite the detect function and decide whether to exclude the detected item
$excluded = apply_filters( 'permalink_manager_excluded_element_id', $excluded, $element_object, $old_query, $pm_query );
// Make sure the loop does not execute infinitely (limit it to 20 iterations)
$uri_query_iteration ++;
if ( $uri_query_iteration === 25 ) {
break;
}
} // If the detected element was excluded repeat the URI query and try to find a new one
while ( ! empty( $excluded ) );
/**
* 3A. Endpoints
*/
if ( ! empty( $element_id ) && empty( $disabled ) && ( ! empty( $endpoint ) || ! empty( $endpoint_value ) ) ) {
if ( is_array( $endpoint ) ) {
foreach ( $endpoint as $endpoint_name => $endpoint_value ) {
$query[ $endpoint_name ] = $endpoint_value;
}
} else if ( $endpoint == 'feed' ) {
$feed_rewrite = true;
// Check if /feed/ endpoint is allowed for selected post type or taxonomy
if ( ! empty( $post_type_object ) && is_array( $post_type_object->rewrite ) && empty( $post_type_object->rewrite['feeds'] ) ) {
$feed_rewrite = false;
}
if ( $feed_rewrite ) {
$query[ $endpoint ] = 'feed';
} else {
$element_id = '';
$query = array(
'error' => 404
);
}
} else if ( $endpoint == 'embed' ) {
$query[ $endpoint ] = true;
} else if ( $endpoint == 'page' ) {
$endpoint = 'paged';
if ( is_numeric( $endpoint_value ) ) {
$query[ $endpoint ] = $endpoint_value;
} else {
$query = $old_query;
}
} else if ( $endpoint == 'trackback' ) {
$endpoint = 'tb';
$query[ $endpoint ] = 1;
} else if ( empty( $endpoint ) && is_numeric( $endpoint_value ) ) {
$query['page'] = $endpoint_value;
} else {
$query[ $endpoint ] = $endpoint_value;
}
// Fix for attachments
if ( ! empty( $query['attachment'] ) ) {
$query = array( 'attachment' => $query['attachment'], 'do_not_redirect' => 1 );
} else if ( isset( $query['attachment'] ) ) {
$query = $old_query;
$element_id = false;
}
}
/**
* 3B. Endpoints - check if any endpoint is set with $_GET parameter
*/
if ( ! empty( $element_id ) && $deep_detect_enabled && ! empty( $_GET ) ) {
$get_endpoints = array_intersect( $wp->public_query_vars, array_keys( $_GET ) );
if ( ! empty( $get_endpoints ) ) {
// Append query vars from $_GET parameters
foreach ( $get_endpoints as $endpoint ) {
// Numeric endpoints
$endpoint_value = ( in_array( $endpoint, array( 'page', 'paged', 'attachment_id' ) ) ) ? filter_var( $_GET[ $endpoint ], FILTER_SANITIZE_NUMBER_INT ) : $_GET[ $endpoint ];
// Ignore page endpoint if its value is empty or equal to 1
if ( in_array( $endpoint, array( 'page', 'paged' ) ) && ( empty( $endpoint_value ) || $endpoint_value == 1 ) ) {
continue;
}
// Replace whitespaces with '+' (for YITH WooCommerce Ajax Product Filter URLs only) and sanitize the value
$endpoint_value = ( isset( $_GET['yith_wcan'] ) ) ? preg_replace( '/\s+/', '+', $endpoint_value ) : $endpoint_value;
$query[ $endpoint ] = sanitize_text_field( $endpoint_value );
}
}
}
/**
* 4. Set global with detected item id
*/
if ( ! empty( $element_id ) && empty( $disabled ) && empty( $excluded ) ) {
if ( ! empty( $element_object->taxonomy ) ) {
$pm_query['id'] = $element_object->term_id;
$content_type = "Taxonomy: {$element_object->taxonomy}";
} else if ( ! empty( $element_object->post_type ) ) {
$pm_query['id'] = $element_object->ID;
$content_type = "Post type: {$element_object->post_type}";
}
// If language mismatch is detected do not set 'do_not_redirect' to allow canonical redirect
if ( is_array( $query ) && ( empty( $pm_query['flag'] ) || $pm_query['flag'] !== 'language_mismatch' ) ) {
$query['do_not_redirect'] = 1;
}
}
}
/**
* 5. Debug data
*/
if ( empty ( $element_object ) || empty ( $content_type ) ) {
$content_type = $element_object = '';
}
$uri_parts = ( ! empty( $uri_parts ) ) ? $uri_parts : '';
$query = apply_filters( 'permalink_manager_filter_query', $query, $old_query, $uri_parts, $pm_query, $content_type, $element_object );
if ( $return_object && ! empty( $element_object ) ) {
return $element_object;
} else {
return $query;
}
}
/**
* Trailing slash & remove BOM and double slashes
*
* @param string $permalink
*
* @return string
*/
static function control_trailing_slashes( $permalink ) {
global $permalink_manager_options;
// Ignore empty & numeric permalinks
if ( empty( $permalink ) || is_numeric( $permalink ) ) {
return $permalink;
}
// Keep the original permalink in a separate variable
$original_permalink = $permalink;
$permalink_path = parse_url( $original_permalink, PHP_URL_PATH );
$permalink_path = ( ! empty( $permalink_path ) ) ? trim( $permalink_path, '/' ) : $permalink_path;
$trailing_slash_mode = ( ! empty( $permalink_manager_options['general']['trailing_slashes'] ) ) ? $permalink_manager_options['general']['trailing_slashes'] : "";
// Ignore homepage URLs
if ( filter_var( $permalink, FILTER_VALIDATE_URL ) && empty( $permalink_path ) ) {
return $permalink;
}
// Always remove trailing slashes from URLs/URIs that end with file extension (e.g. html)
if ( preg_match( '/(http(?:s)\:\/\/[^\/]+\/)?.*\.([a-zA-Z]{3,4})[\/]*(\?[^\/]+|$)/', $permalink ) ) {
$trailing_slash_mode = 2;
}
// Add trailing slashes
if ( in_array( $trailing_slash_mode, array( 1, 10 ) ) ) {
$permalink = preg_replace( '/(.+?)([\/]*)([\?\#][^\/]+|$)/', '$1/$3', $permalink ); // Instead of trailingslashit()
} // Remove trailing slashes
else if ( in_array( $trailing_slash_mode, array( 2, 20 ) ) ) {
$permalink = preg_replace( '/(.+?)([\/]*)([\?\#][^\/]+|$)/', '$1$3', $permalink ); // Instead of untrailingslashit()
} // Default settings
else {
$permalink = user_trailingslashit( $permalink );
}
// Remove double slashes
$permalink = preg_replace( '/(?<!:)(\/{2,})/', '/', $permalink );
// Remove trailing slashes from URLs that end with query string or anchors
$permalink = preg_replace( '/([\?\#]{1}[^\/]+)([\/]+)$/', '$1', $permalink );
return apply_filters( 'permalink_manager_control_trailing_slashes', $permalink, $original_permalink );
}
/**
* Display 404 if requested page does not exist in pagination or the pagination format is incorrect
*/
function fix_pagination_pages() {
global $wp_query, $pm_query, $post, $permalink_manager_options;
// 1. Check if the custom permalink was detected
if ( empty( $pm_query['id'] ) ) {
return;
}
// 2. Get the queried object
$object = get_queried_object();
if ( ! empty( $object ) && ! empty( $object->taxonomy ) ) {
$term = $object;
} else if ( ! empty( $object->post_type ) ) {
$post = $object;
} else if ( empty( $object ) && ! empty( $wp_query->post ) ) {
$post = $wp_query->post;
} else {
return;
}
// 3.1. Validate the pages count
if ( ( ! empty( $post->post_type ) && isset( $post->post_content ) ) || ( isset( $wp_query->max_num_pages ) && ! empty( $term->taxonomy ) ) ) {
$current_page = ( ! empty( $wp_query->query_vars['page'] ) ) ? $wp_query->query_vars['page'] : 1;
$current_page = ( empty( $wp_query->query_vars['page'] ) && ! empty( $wp_query->query_vars['paged'] ) ) ? $wp_query->query_vars['paged'] : $current_page;
// 2.1B. Count post pages
$post_content = ( ! empty( $post->post_content ) ) ? $post->post_content : '';
$num_pages = ( is_home() || is_archive() || is_search() ) ? $wp_query->max_num_pages : substr_count( strtolower( $post_content ), '<!--nextpage-->' ) + 1;
$is_404 = ( $current_page > 1 && ( $current_page > $num_pages ) ) ? true : false;
} // 3.2. Force 404 if no posts are loaded
else if ( ! empty( $wp_query->query['paged'] ) && $wp_query->post_count == 0 ) {
$is_404 = true;
}
// 3.4. Force 404 if endpoint value is not set or not numeric
if ( ! empty( $pm_query['endpoint'] ) && $pm_query['endpoint'] == 'page' && ( empty( $pm_query['endpoint_value'] ) || ! is_numeric( $pm_query['endpoint_value'] ) ) ) {
$is_404 = true;
}
// 4. Block non-existent pages (Force 404 error or allow canonical redirect)
if ( ! empty( $is_404 ) ) {
$pagination_mode = ( ! empty( $permalink_manager_options['general']['pagination_redirect'] ) ) ? $permalink_manager_options['general']['pagination_redirect'] : false;
// Make sure that canonical redirect is not disabled in adjust_canonical_redirect() method
if ( $pagination_mode == 2 ) {
$wp_query->query_vars['do_not_redirect'] = 0;
} else {
$wp_query->query = $wp_query->queried_object = $wp_query->queried_object_id = $pm_query = $post = null;
$wp_query->set_404();
status_header( 404 );
nocache_headers();
}
$pm_query = '';
}
}
/**
* Enhance the existing canonical redirect functionality, allowing users define custom redirects and support custom permalinks
*/
function new_uri_redirect_and_404() {
global $wp_query, $wp, $wp_rewrite, $wpdb, $permalink_manager_uris, $permalink_manager_redirects, $permalink_manager_external_redirects, $permalink_manager_options, $pm_query;
// Get the redirection mode & trailing slashes settings
$redirect_mode = ( ! empty( $permalink_manager_options['general']['redirect'] ) ) ? $permalink_manager_options['general']['redirect'] : false;
$trailing_slashes_mode = ( ! empty( $permalink_manager_options['general']['trailing_slashes'] ) ) ? $permalink_manager_options['general']['trailing_slashes'] : false;
$trailing_slashes_redirect = ( ! empty( $permalink_manager_options['general']['trailing_slashes_redirect'] ) ) ? $permalink_manager_options['general']['trailing_slashes_redirect'] : false;
$extra_redirects = ( ! empty( $permalink_manager_options['general']['extra_redirects'] ) ) ? $permalink_manager_options['general']['extra_redirects'] : false;
$canonical_redirect = ( ! empty( $permalink_manager_options['general']['canonical_redirect'] ) ) ? $permalink_manager_options['general']['canonical_redirect'] : false;
$old_slug_redirect = ( ! empty( $permalink_manager_options['general']['old_slug_redirect'] ) ) ? $permalink_manager_options['general']['old_slug_redirect'] : false;
$endpoint_redirect = ( ! empty( $permalink_manager_options['general']['endpoint_redirect'] ) ) ? $permalink_manager_options['general']['endpoint_redirect'] : false;
$copy_query_redirect = ( ! empty( $permalink_manager_options['general']['copy_query_redirect'] ) ) ? $permalink_manager_options['general']['copy_query_redirect'] : false;
$redirect_type = '-';
// Get home URL
$home_url = rtrim( get_option( 'home' ), "/" );
$home_dir = parse_url( $home_url, PHP_URL_PATH );
// Set up $correct_permalink variable
$correct_permalink = '';
// Get query string & URI
if ( empty( $_SERVER['REQUEST_URI'] ) ) {
return;
}
$query_string = ( $copy_query_redirect && ! empty( $_SERVER['QUERY_STRING'] ) ) ? $_SERVER['QUERY_STRING'] : '';
$old_uri = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
$old_uri = $old_uri_abs = ( empty( $old_uri ) ) ? strtok( $_SERVER["REQUEST_URI"], '?' ) : $old_uri;
// Fix for WP installed in directories (remove the directory name from the URI)
if ( ! empty( $home_dir ) ) {
$home_dir_regex = preg_quote( trim( $home_dir ), "/" );
$old_uri = preg_replace( "/{$home_dir_regex}/", "", $old_uri, 1 );
}
if ( is_front_page() || ( is_page() && get_option( 'show_on_front' ) === 'page' && get_queried_object_id() === (int) get_option( 'page_on_front' ) ) ) {
$is_front_page = true;
} else {
$is_front_page = false;
}
// Do not use custom redirects on author pages, search & front page
if ( ! is_author() && ! $is_front_page && ! is_home() && ! is_feed() && ! is_search() && empty( $_GET['s'] ) ) {
// Sometimes $wp_query indicates the wrong object if requested directly
$queried_object = get_queried_object();
// Unset 404 if custom URI is detected
if ( ! empty( $pm_query['id'] ) && ! empty( $queried_object->post_status ) ) {
$wp_query->is_404 = false;
}
/**
* 1A. External redirect
*/
if ( ! empty( $pm_query['id'] ) ) {
if ( ! empty( $queried_object->ID ) && ! empty( $permalink_manager_external_redirects[ $queried_object->ID ] ) ) {
$external_url = $permalink_manager_external_redirects[ $queried_object->ID ];
} else if ( ! empty( $queried_object->term_id ) && ! empty( $permalink_manager_external_redirects["tax-{$queried_object->term_id}"] ) ) {
$external_url = $permalink_manager_external_redirects["tax-{$queried_object->term_id}"];
}
if ( ! empty( $external_url ) && filter_var( $external_url, FILTER_VALIDATE_URL ) ) {
// Allow redirect
$wp_query->query_vars['do_not_redirect'] = 0;
wp_redirect( $external_url, 301, PERMALINK_MANAGER_PLUGIN_NAME ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- By design, the users can redirect the post/term to any external URL here
exit();
}
}
/**
* 1B. Custom redirects
*/
if ( empty( $wp_query->query_vars['do_not_redirect'] ) && $extra_redirects && ! empty( $permalink_manager_redirects ) && is_array( $permalink_manager_redirects ) && ! empty( $wp->request ) && ! empty( $pm_query['uri'] ) ) {
$uri = $pm_query['uri'];
$endpoint_value = $pm_query['endpoint_value'];
// Make sure that URIs with non-ASCII characters are also detected + Check the URLs that end with number
$decoded_url = urldecode( $uri );
$endpoint_url = "{$uri}/{$endpoint_value}";
// Convert to lowercase to make case-insensitive
$force_lowercase = apply_filters( 'permalink_manager_force_lowercase_uris', true );
if ( $force_lowercase ) {
$uri = strtolower( $uri );
$decoded_url = strtolower( $decoded_url );
$endpoint_url = strtolower( $endpoint_url );
}
// Check if the URI is not assigned to any post/term's redirects
foreach ( $permalink_manager_redirects as $element => $redirects ) {
if ( ! is_array( $redirects ) ) {
continue;
}
if ( in_array( $uri, $redirects ) || in_array( $decoded_url, $redirects ) || ( is_numeric( $endpoint_value ) && in_array( $endpoint_url, $redirects ) ) ) {
// Post is detected
if ( is_numeric( $element ) ) {
$correct_permalink = get_permalink( $element );
} // Term is detected
else {
$term_id = intval( preg_replace( "/[^0-9]/", "", $element ) );
$correct_permalink = get_term_link( $term_id );
}
// The custom redirect is found so there is no need to query the rest of array
break;
}
}
$redirect_type = ( ! empty( $correct_permalink ) ) ? 'custom_redirect' : $redirect_type;
}
// Ignore WP-Content links
if ( strpos( $_SERVER['REQUEST_URI'], '/wp-content' ) !== false ) {
return;
}
/**
* 1C. Enhance native redirect
*/
if ( $canonical_redirect && empty( $wp_query->query_vars['do_not_redirect'] ) && ! empty( $queried_object ) && empty( $correct_permalink ) ) {
// Affect only posts with custom URI and old URIs
if ( ! empty( $queried_object->ID ) && isset( $permalink_manager_uris[ $queried_object->ID ] ) && empty( $wp_query->query['preview'] ) ) {
// Ignore posts with specific statuses
if ( ! ( empty( $queried_object->post_status ) ) && in_array( $queried_object->post_status, array( 'draft', 'pending', 'auto-draft', 'future' ) ) ) {
return;
}
// Check if the post is excluded
if ( Permalink_Manager_Helper_Functions::is_post_excluded( $queried_object ) ) {
return;
}
// Get the real URL
$correct_permalink = get_permalink( $queried_object->ID );
} // Affect only terms with custom URI and old URIs
else if ( ! empty( $queried_object->term_id ) && isset( $permalink_manager_uris["tax-{$queried_object->term_id}"] ) && defined( 'PERMALINK_MANAGER_PRO' ) ) {
// Check if the term is excluded
if ( Permalink_Manager_Helper_Functions::is_term_excluded( $queried_object ) ) {
return;
}
// Get the real URL
$correct_permalink = get_term_link( $queried_object->term_id, $queried_object->taxonomy );
}
$redirect_type = ( ! empty( $correct_permalink ) ) ? 'native_redirect' : $redirect_type;
}
/**
* 1D. Old slug redirect
*/
if ( $old_slug_redirect && ! empty( $pm_query['uri'] ) && empty( $wp_query->query_vars['do_not_redirect'] ) && is_404() && empty( $correct_permalink ) ) {
$slug = basename( $pm_query['uri'] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct SQL query is faster here
$post_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id from {$wpdb->postmeta} WHERE meta_key = '_wp_old_slug' AND meta_value = %s", $slug ) );
if ( ! empty( $post_id ) && Permalink_Manager_Helper_Functions::is_post_excluded( $post_id, true, true ) !== true ) {
$correct_permalink = get_permalink( $post_id );
$redirect_type = 'old_slug_redirect';
}
}
/**
* 2. Prevent redirect loop
*/
if ( ! empty( $correct_permalink ) && is_string( $correct_permalink ) && ! empty( $wp->request ) && $redirect_type != 'slash_redirect' ) {
$current_uri = trim( $wp->request, "/" );
$redirect_uri = trim( parse_url( $correct_permalink, PHP_URL_PATH ), "/" );
$correct_permalink = ( $redirect_uri == $current_uri ) ? null : $correct_permalink;
}
/**
* 3. Add endpoints to redirect URL
*/
if ( ! empty( $correct_permalink ) && $endpoint_redirect && ( ! empty( $pm_query['endpoint_value'] ) || ! empty( $pm_query['endpoint'] ) ) ) {
$endpoint_value = $pm_query['endpoint_value'];
if ( empty( $pm_query['endpoint'] ) && is_numeric( $endpoint_value ) ) {
$correct_permalink = sprintf( "%s/%d", trim( $correct_permalink, "/" ), $endpoint_value );
} else if ( isset( $pm_query['endpoint'] ) && ! empty( $endpoint_value ) ) {
if ( $pm_query['endpoint'] == 'cpage' ) {
$correct_permalink = sprintf( "%s/%s-%s", trim( $correct_permalink, "/" ), $wp_rewrite->comments_pagination_base, $endpoint_value );
} else {
$correct_permalink = sprintf( "%s/%s/%s", trim( $correct_permalink, "/" ), $pm_query['endpoint'], $endpoint_value );
}
} else {
$correct_permalink = sprintf( "%s/%s", trim( $correct_permalink, "/" ), $pm_query['endpoint'] );
}
}
} else {
$queried_object = '-';
}
/**
* 4. Check trailing & duplicated slashes (ignore links with query parameters)
*/
if ( ( ( $trailing_slashes_mode && $trailing_slashes_redirect ) || preg_match( '/\/{2,}/', $old_uri ) ) && empty( $is_front_page ) && empty( $_POST ) && empty( $correct_permalink ) && empty( $query_string ) && ! empty( $old_uri ) && $old_uri !== "/" ) {
$trailing_slash = ( substr( $old_uri, - 1 ) == "/" ) ? true : false;
$obsolete_slash = ( preg_match( '/\/{2,}/', $old_uri ) || preg_match( "/.*\.([a-zA-Z]{3,4})\/$/", $old_uri ) );
if ( ( $trailing_slashes_mode == 1 && ! $trailing_slash ) || ( $trailing_slashes_mode == 2 && $trailing_slash ) || $obsolete_slash ) {
$new_uri = self::control_trailing_slashes( $old_uri );
if ( $new_uri !== $old_uri ) {
$correct_permalink = sprintf( "%s/%s", $home_url, ltrim( $new_uri, '/' ) );
$redirect_type = 'slash_redirect';
}
}
}
/**
* 5. WWW prefix | SSL mismatch redirect
*/
if ( ! empty( $permalink_manager_options['general']['sslwww_redirect'] ) && ! empty( $_SERVER['HTTP_HOST'] ) ) {
$home_url_has_www = ( strpos( $home_url, 'www.' ) !== false ) ? true : false;
$requested_url_has_www = ( strpos( $_SERVER['HTTP_HOST'], 'www.' ) !== false ) ? true : false;
$home_url_has_ssl = ( strpos( $home_url, 'https' ) !== false ) ? true : false;
if ( ( $home_url_has_www !== $requested_url_has_www ) || ( ! is_ssl() && $home_url_has_ssl !== false ) ) {
$new_uri = ltrim( $old_uri, '/' );
$correct_permalink = sprintf( "%s/%s", $home_url, $new_uri );
$redirect_type = 'www_redirect';
}
}
/**
* 6. Debug redirect
*/
$correct_permalink = apply_filters( 'permalink_manager_filter_redirect', $correct_permalink, $redirect_type, $queried_object, $old_uri );
/**
* 7. Ignore default URIs (or do nothing if redirects are disabled)
*/
if ( ! empty( $correct_permalink ) && is_string( $correct_permalink ) && ! empty( $redirect_mode ) ) {
// Allow redirect
$wp_query->query_vars['do_not_redirect'] = 0;
// Adjust trailing slashes
$correct_permalink = self::control_trailing_slashes( $correct_permalink );
// Prevent redirect loop
$rel_old_uri = wp_make_link_relative( $old_uri_abs );
$rel_new_uri = wp_make_link_relative( $correct_permalink );
// Append query string
$correct_permalink = ( ! empty( $query_string ) ) ? sprintf( "%s?%s", strtok( $correct_permalink, "?" ), $query_string ) : $correct_permalink;
if ( filter_var( $correct_permalink, FILTER_VALIDATE_URL ) && ( $redirect_type === 'www_redirect' || $rel_old_uri !== $rel_new_uri ) ) {
wp_safe_redirect( $correct_permalink, $redirect_mode, PERMALINK_MANAGER_PLUGIN_NAME );
exit();
}
}
}
/**
* Control how the canonical redirect function in WordPress and other popular plugins works
*/
function adjust_canonical_redirect() {
global $permalink_manager_options, $wp, $wp_query, $wp_rewrite;
// Adjust rewrite settings for trailing slashes
$trailing_slash_setting = ( ! empty( $permalink_manager_options['general']['trailing_slashes'] ) ) ? $permalink_manager_options['general']['trailing_slashes'] : "";
if ( in_array( $trailing_slash_setting, array( 1, 10 ) ) ) {
$wp_rewrite->use_trailing_slashes = true;
} else if ( in_array( $trailing_slash_setting, array( 2, 20 ) ) ) {
$wp_rewrite->use_trailing_slashes = false;
}
// Get endpoints
$endpoints = Permalink_Manager_Helper_Functions::get_endpoints();
$endpoints_array = ( $endpoints ) ? explode( "|", $endpoints ) : array();
// Check if any endpoint is called (fix for endpoints)
foreach ( $endpoints_array as $endpoint ) {
if ( ! empty( $wp->query_vars[ $endpoint ] ) && ! in_array( $endpoint, array( 'attachment', 'page', 'paged', 'feed' ) ) ) {
$wp_query->query_vars['do_not_redirect'] = 1;
break;
}
}
// Allow canonical redirect for ../1/ and ../page/1/
if ( ( ! empty( $wp->query_vars['paged'] ) && $wp->query_vars['paged'] == 1 ) || ( ! empty( $wp->query_vars['page'] ) && $wp->query_vars['page'] == 1 ) ) {
$wp_query->query_vars['do_not_redirect'] = 0;
} // Allow canonical redirect for URL with specific query parameters
else if ( ( is_single() && ( ! empty( $_GET['p'] ) || ! empty( $_GET['name'] ) ) ) || ( is_page() && ! empty( $_GET['page_id'] ) ) ) {
$wp_query->query_vars['do_not_redirect'] = 0;
}
if ( empty( $permalink_manager_options['general']['canonical_redirect'] ) ) {
remove_action( 'template_redirect', 'redirect_canonical' );
}
if ( empty( $permalink_manager_options['general']['old_slug_redirect'] ) ) {
remove_action( 'template_redirect', 'wp_old_slug_redirect' );
}
if ( ! empty( $wp_query->query_vars['do_not_redirect'] ) ) {
if ( function_exists( 'rank_math' ) && ! empty( $permalink_manager_options['general']['rankmath_redirect'] ) ) {
$rank_math_instance = rank_math();
if ( ! empty( $rank_math_instance->manager ) && is_object( $rank_math_instance->manager ) && method_exists( $rank_math_instance->manager, 'get_module' ) ) {
$rank_math_redirections_module = $rank_math_instance->manager->get_module( 'redirections' );
if ( ! empty( $rank_math_redirections_module ) ) {
remove_action( 'template_redirect', array( $rank_math_redirections_module, 'do_redirection' ), 11 );
remove_action( 'wp', array( $rank_math_redirections_module, 'do_redirection' ), 11 );
}
}
}
// SEOPress
remove_action( 'template_redirect', 'seopress_category_redirect', 1 );
remove_action( 'template_redirect', 'wp_old_slug_redirect' );
remove_action( 'template_redirect', 'redirect_canonical' );
add_filter( 'wpml_is_redirected', '__return_false', 99, 2 );
add_filter( 'pll_check_canonical_url', '__return_false', 99, 2 );
}
}
/**
* Enable case-insensitive permalinks mode
*/
function case_insensitive_permalinks() {
global $permalink_manager_uris;
if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
$_SERVER['REQUEST_URI'] = strtolower( $_SERVER['REQUEST_URI'] );
$permalink_manager_uris = array_map( 'strtolower', $permalink_manager_uris );
}
}
}
@@ -0,0 +1,190 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Additional debug functions for "Permalink Manager Pro"
*/
class Permalink_Manager_Debug_Functions {
public function __construct() {
add_action( 'init', array( $this, 'debug_data' ), 99 );
}
/**
* Map the debug functions to specific hooks
*/
public function debug_data() {
global $permalink_manager_options;
if ( ! empty( $permalink_manager_options['general']['debug_mode'] ) || current_user_can( 'manage_options' ) ) {
add_filter( 'permalink_manager_filter_query', array( $this, 'debug_query' ), 9, 5 );
add_filter( 'permalink_manager_filter_redirect', array( $this, 'debug_redirect' ), 9, 3 );
add_filter( 'wp_redirect', array( $this, 'debug_wp_redirect' ), 9, 2 );
if ( current_user_can( 'manage_options' ) ) {
self::debug_custom_fields();
}
}
}
/**
* Debug the WordPress query filtered in the Permalink_Manager_Core_Functions::detect_post(); function
*
* @param array $query
* @param array $old_query
* @param array $uri_parts
* @param array $pm_query
* @param string $content_type
*
* @return array
*/
public function debug_query( $query, $old_query = null, $uri_parts = null, $pm_query = null, $content_type = null ) {
global $permalink_manager;
if ( isset( $_REQUEST['debug_url'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No data is saved here
$debug_info['uri_parts'] = $uri_parts;
$debug_info['old_query_vars'] = $old_query;
$debug_info['new_query_vars'] = $query;
$debug_info['pm_query'] = ( ! empty( $pm_query['id'] ) ) ? $pm_query['id'] : "-";
$debug_info['content_type'] = ( ! empty( $content_type ) ) ? $content_type : "-";
// License key info
if ( class_exists( 'Permalink_Manager_Pro_License' ) ) {
$license_key = $permalink_manager->functions['pro-license']->get_license_key();
// Mask the license key
$debug_info['license_key'] = preg_replace( '/([^-]+)-([^-]+)-([^-]+)-([^-]+)$/', '***-***-$3', $license_key );
}
// Plugin version
$debug_info['version'] = PERMALINK_MANAGER_VERSION;
self::display_debug_data( $debug_info );
}
return $query;
}
/**
* Debug the redirect controlled by Permalink_Manager_Core_Functions::new_uri_redirect_and_404();
*
* @param string $correct_permalink
* @param string $redirect_type
* @param mixed $queried_object
*
* @return string
*/
public function debug_redirect( $correct_permalink, $redirect_type, $queried_object ) {
if ( isset( $_REQUEST['debug_redirect'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No data is saved here
$debug_info['redirect_url'] = ( ! empty( $correct_permalink ) ) ? $correct_permalink : '-';
$debug_info['redirect_type'] = ( ! empty( $redirect_type ) ) ? $redirect_type : "-";
self::display_debug_data( $debug_info );
}
return $correct_permalink;
}
/**
* Debug wp_redirect() function used in 3rd party plugins
*
* @param string $url
* @param string $status
*
* @return string
*/
public function debug_wp_redirect( $url, $status ) {
if ( isset( $_GET['debug_wp_redirect'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No data is saved here
$debug_info['url'] = $url;
$debug_info['status'] = $status;
$debug_info['backtrace'] = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 10 ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Backtrace is a part of debug tools
self::display_debug_data( $debug_info );
}
return $url;
}
/**
* Display the list of all custom fields assigned to specific post
*/
public static function debug_custom_fields() {
global $pagenow;
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- No data is saved here
if ( ! isset( $_GET['debug_custom_fields'] ) ) {
return;
}
if ( $pagenow == 'post.php' && isset( $_GET['post'] ) ) {
$post_id = intval( $_GET['post'] );
$custom_fields = get_post_meta( $post_id );
}
if ( $pagenow == 'term.php' && isset( $_GET['tag_ID'] ) ) {
$term_id = intval( $_GET['tag_ID'] );
$custom_fields = get_term_meta( $term_id );
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( isset ( $custom_fields ) ) {
self::display_debug_data( $custom_fields );
}
}
/**
* A helper function used to display the debug data in various functions
*
* @param mixed $debug_info
*/
public static function display_debug_data( $debug_info ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- print_r() is a part of debug tool
$debug_txt = print_r( $debug_info, true );
$debug_txt = sprintf( "<pre style=\"display:block;\">%s</pre>", esc_html( $debug_txt ) );
wp_die( wp_kses_post( $debug_txt ) );
}
/**
* Generate a CSV file from array
*
* @param array $array
* @param string $filename
*/
public static function output_csv( $array, $filename = 'debug.csv', $delimiter = ',' ) {
if ( count( $array ) == 0 ) {
return null;
}
// Disable caching
$now = gmdate( "D, d M Y H:i:s" );
header( "Expires: Tue, 03 Jul 2001 06:00:00 GMT" );
header( "Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate" );
header( "Last-Modified: {$now} GMT" );
// Force download
header( "Content-Type: application/force-download" );
header( "Content-Type: application/octet-stream" );
header( "Content-Type: application/download" );
header( 'Content-Type: text/csv; charset=utf-8' );
// Disposition / encoding on response body
header( "Content-Disposition: attachment;filename={$filename}" );
header( "Content-Transfer-Encoding: binary" );
$df = fopen( "php://output", 'w' );
// Use the same delimiter for headers and data
fputcsv( $df, array_keys( reset( $array ) ), $delimiter );
foreach ( $array as $row ) {
fputcsv( $df, $row, $delimiter );
}
fclose( $df ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
die();
}
}
@@ -0,0 +1,73 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Support for Gutenberg editor
*/
class Permalink_Manager_Gutenberg {
public function __construct() {
add_action( 'enqueue_block_editor_assets', array( $this, 'init' ) );
add_action( 'wp_ajax_pm_get_uri_editor', array( $this, 'get_uri_editor' ) );
add_action( 'wp_ajax_nopriv_pm_get_uri_editor', array( $this, 'get_uri_editor' ) );
}
/**
* Add URI Editor meta box to Gutenberg editor
*/
public function init() {
global $current_screen, $post;
// Get displayed post type
if ( empty( $current_screen->post_type ) || empty( $post->post_type ) ) {
return;
}
// Stop the hook (if needed)
$show_uri_editor = apply_filters( "permalink_manager_show_uri_editor_post", true, $post, $post->post_type );
if ( ! $show_uri_editor ) {
return;
}
// Check the user capabilities
if ( Permalink_Manager_Admin_Functions::current_user_can_edit_uris() === false ) {
return;
}
// Check if the post is excluded
if ( ! empty( $post->ID ) && Permalink_Manager_Helper_Functions::is_post_excluded( $post ) ) {
return;
}
add_meta_box( 'permalink-manager', __( 'Permalink Manager', 'permalink-manager' ), array( $this, 'get_uri_editor' ), '', 'side', 'high' );
// wp_enqueue_script( 'permalink-manager-gutenberg', PERMALINK_MANAGER_URL . '/out/permalink-manager-gutenberg.js', array( 'wp-plugins', 'wp-edit-post', 'wp-i18n', 'wp-element' ) );
// wp_enqueue_style( 'permalink-manager-gutenberg', PERMALINK_MANAGER_URL . '/out/permalink-manager-gutenberg.css', array(), PERMALINK_MANAGER_VERSION );
}
/**
* Display the URI Editor for specific post
*
* @param WP_Post|int $post
*/
public function get_uri_editor( $post = null ) {
if ( empty( $post->ID ) && empty( $_REQUEST['post_id'] ) ) {
return;
} else if ( ! empty( $_REQUEST['post_id'] ) && is_numeric( $_REQUEST['post_id'] ) ) {
$post = get_post( $_REQUEST['post_id'] );
}
// Check if the user can edit this post
if ( ! empty( $post->ID ) && current_user_can( 'edit_post', $post->ID ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo ( $post ) ? Permalink_Manager_UI_Elements::display_uri_box( $post, true ) : '';
}
if ( wp_doing_ajax() && isset( $_GET['action'] ) && $_GET['action'] == 'pm_get_uri_editor' ) {
die();
}
}
}
@@ -0,0 +1,984 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Helper functions used in classes and another subclasses
*/
class Permalink_Manager_Helper_Functions {
public function __construct() {
add_action( 'plugins_loaded', array( $this, 'early_init' ), 5 );
add_action( 'plugins_loaded', array( $this, 'init' ), 20 );
}
/**
* Add early hooks used by plugin to filter the custom permalinks
*/
public function early_init() {
// Reload the globals when the blog is switched (multisite)
add_action( 'switch_blog', array( $this, 'reload_globals_in_network' ), 9 );
}
/**
* Add hooks used by plugin to filter the custom permalinks
*/
public function init() {
global $permalink_manager_options;
// Clear the final default URIs
add_filter( 'permalink_manager_filter_default_term_uri', array( $this, 'clear_single_uri' ), 20 );
add_filter( 'permalink_manager_filter_default_post_uri', array( $this, 'clear_single_uri' ), 20 );
// Force unique URIs when saving them
if ( ! empty( $permalink_manager_options['general']['force_unique_uris'] ) ) {
add_filter( 'permalink_manager_pre_update_post_uri', array( $this, 'force_unique_uri' ), 100, 3 );
add_filter( 'permalink_manager_filter_default_post_uri', array( $this, 'force_unique_uri' ), 100, 3 );
add_filter( 'permalink_manager_pre_update_term_uri', array( $this, 'force_unique_uri' ), 100, 3 );
add_filter( 'permalink_manager_filter_default_term_uri', array( $this, 'force_unique_uri' ), 100, 3 );
}
}
/**
* Get the primary term for the specific post
*
* @param int $post_id
* @param string $taxonomy
* @param bool $slug_only
*
* @return array|string|WP_Term
*/
static function get_primary_term( $post_id, $taxonomy, $slug_only = true ) {
global $permalink_manager_options;
$primary_term_enabled = ( isset( $permalink_manager_options['general']['primary_category'] ) ) ? (bool) $permalink_manager_options['general']['primary_category'] : true;
$primary_term_enabled = apply_filters( 'permalink_manager_primary_term', $primary_term_enabled );
if ( ! $primary_term_enabled ) {
return '';
}
// A. Yoast SEO
if ( class_exists( 'WPSEO_Primary_Term' ) ) {
$yoast_primary_term_label = sprintf( 'yoast_wpseo_primary_%s_term', $taxonomy );
// Hotfix: Yoast SEO saves the primary term using 'save_post' hook with the highest priority, so the primary term ID is taken directly from $_POST
if ( ! empty( $_POST[ $yoast_primary_term_label ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$yoast_primary_term_id = filter_input( INPUT_POST, $yoast_primary_term_label, FILTER_SANITIZE_NUMBER_INT );
} else {
$yoast_primary_term = new WPSEO_Primary_Term( $taxonomy, $post_id );
$yoast_primary_term_id = $yoast_primary_term->get_primary_term();
}
$primary_term = ( is_numeric( $yoast_primary_term_id ) ) ? get_term( $yoast_primary_term_id, $taxonomy ) : '';
} // B. The SEO Framework
else if ( function_exists( 'the_seo_framework' ) || function_exists( 'tsf' ) ) {
if ( class_exists( 'The_SEO_Framework\Data\Plugin\Post' ) ) {
$primary_term = The_SEO_Framework\Data\Plugin\Post::get_primary_term( $post_id, $taxonomy );
} elseif ( function_exists( 'the_seo_framework' ) && method_exists( the_seo_framework(), 'get_primary_term' ) ) {
$primary_term = the_seo_framework()->get_primary_term( $post_id, $taxonomy );
}
} // C. RankMath
else if ( class_exists( 'RankMath' ) ) {
$primary_cat_id = get_post_meta( $post_id, "rank_math_primary_{$taxonomy}", true );
$primary_term = ( ! empty( $primary_cat_id ) ) ? get_term( $primary_cat_id, $taxonomy ) : '';
} // D. SEOPress
else if ( function_exists( 'seopress_init' ) && $taxonomy == 'category' ) {
$primary_cat_id = get_post_meta( $post_id, '_seopress_robots_primary_cat', true );
$primary_term = ( ! empty( $primary_cat_id ) ) ? get_term( $primary_cat_id, 'category' ) : '';
} // E. SmartCrawler
else if ( class_exists( '\SmartCrawl\SmartCrawl' ) ) {
$primary_cat_id = get_post_meta( $post_id, "wds_primary_{$taxonomy}", true );
$primary_term = ( ! empty( $primary_cat_id ) ) ? get_term( $primary_cat_id, $taxonomy ) : '';
} // F. All In One SEO Pro
else if ( class_exists( '\AIOSEO\Plugin\Pro\Standalone\PrimaryTerm' ) ) {
$primary_term_class = new \AIOSEO\Plugin\Pro\Standalone\PrimaryTerm();
$primary_term = ( method_exists( $primary_term_class, 'getPrimaryTerm' ) ) ? $primary_term_class->getPrimaryTerm( $post_id, $taxonomy ) : '';
}
if ( ! empty( $primary_term ) && ! is_wp_error( $primary_term ) ) {
return ( $slug_only ) ? $primary_term->slug : $primary_term;
} else {
return '';
}
}
/**
* Get the lowest level term/post in the specific array
*
* @param WP_Post|WP_Term|int $first_element
* @param array $elements
*
* @return WP_Post|WP_Term|int
*/
static function get_lowest_element( $first_element, $elements ) {
if ( ! empty( $elements ) && ! empty( $first_element ) ) {
// Get the ID of first element
if ( ! empty( $first_element->term_id ) ) {
$first_element_id = $first_element->term_id;
$parent_key = 'parent';
} else if ( ! empty( $first_element->ID ) ) {
$first_element_id = $first_element->ID;
$parent_key = 'post_parent';
} else if ( is_numeric( $first_element ) ) {
$first_element_id = $first_element;
$parent_key = 'post_parent';
} else {
return false;
}
$children = wp_filter_object_list( $elements, array( $parent_key => $first_element_id ) );
if ( ! empty( $children ) ) {
// Get the first term
$child_term = reset( $children );
$first_element = self::get_lowest_element( $child_term, $elements );
}
}
return $first_element;
}
/**
* Get the full (hierarchical) slug for specific term object
*
* @param WP_Term $term
* @param mixed|WP_Term[] $terms
* @param bool $mode
* @param bool $native_uri
*
* @return string
*/
static function get_term_full_slug( $term, $terms, $mode = false, $native_uri = false ) {
global $permalink_manager_uris;
// Check if term is not empty
if ( empty( $term->taxonomy ) ) {
return '';
}
// Get taxonomy
$taxonomy = $term->taxonomy;
// Check if mode is set
if ( empty( $mode ) ) {
$mode = ( is_taxonomy_hierarchical( $taxonomy ) ) ? 2 : 4;
}
// A. Inherit the custom permalink from the term
if ( $mode == 1 ) {
$term_slug = ( ! empty( $permalink_manager_uris["tax-{$term->term_id}"] ) ) ? $permalink_manager_uris["tax-{$term->term_id}"] : '';
} // B. Hierarchical taxonomy base
else if ( $mode == 2 ) {
$ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
$hierarchical_slugs = array();
foreach ( $ancestors as $ancestor ) {
$ancestor_term = get_term( $ancestor, $taxonomy );
$hierarchical_slugs[] = ( $native_uri ) ? $ancestor_term->slug : self::force_custom_slugs( $ancestor_term->slug, $ancestor_term );
}
$hierarchical_slugs = array_reverse( $hierarchical_slugs );
$term_slug = implode( '/', $hierarchical_slugs );
// Append the term slug now
$last_term_slug = ( $native_uri ) ? $term->slug : self::force_custom_slugs( $term->slug, $term );
$term_slug = "{$term_slug}/{$last_term_slug}";
} // C. Force flat taxonomy base - get the highest level term (if %taxonomy_top% tag is used)
else if ( $mode == 3 ) {
if ( ! empty( $term->parent ) ) {
$ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
if ( is_array( $ancestors ) ) {
$top_ancestor = end( $ancestors );
$top_ancestor_term = get_term( $top_ancestor, $taxonomy );
$single_term = ( ! empty( $top_ancestor_term->slug ) ) ? $top_ancestor_term : $term;
}
}
$term_slug = ( ! empty( $single_term->slug ) ) ? self::force_custom_slugs( $single_term->slug, $single_term ) : $term->slug;
} // D. Force flat taxonomy base - get primary or lowest level term (if term is non-hierarchical or %taxonomy_flat% tag is used)
else {
if ( ! empty( $term->slug ) ) {
$term_slug = ( $native_uri ) ? $term->slug : Permalink_Manager_Helper_Functions::force_custom_slugs( $term->slug, $term );
} else if ( ! empty ( $terms ) ) {
foreach ( $terms as $single_term ) {
if ( $single_term->parent == 0 ) {
$term_slug = self::force_custom_slugs( $single_term->slug, $single_term );
break;
}
}
}
}
return ( ! empty( $term_slug ) ) ? $term_slug : "";
}
/**
* Get the full (hierarchical) slug for specific post object
*
* @param WP_Post|int $post
* @param bool $slugs_mode
* @param bool $native_uri
*
* @return string
*/
static function get_post_full_slug( $post, $slugs_mode = false, $native_uri = false ) {
$post = ( ! empty( $post->ID ) ) ? $post : get_post( $post );
if ( ! empty( $post->post_type ) ) {
$full_native_slug = $post->post_name;
$full_custom_slug = Permalink_Manager_Helper_Functions::force_custom_slugs( $post->post_name, $post, false, $slugs_mode );
if ( $post->ancestors && is_post_type_hierarchical( $post->post_type ) ) {
foreach ( $post->ancestors as $parent ) {
$parent = get_post( $parent );
if ( $parent && $parent->post_name ) {
$full_native_slug = $parent->post_name . '/' . $full_native_slug;
$full_custom_slug = Permalink_Manager_Helper_Functions::force_custom_slugs( $parent->post_name, $parent, false, $slugs_mode ) . '/' . $full_custom_slug;
}
}
}
$post_slug = ( $native_uri ) ? $full_native_slug : $full_custom_slug;
}
return ( ! empty( $post_slug ) ) ? $post_slug : "";
}
/**
* Allow to disable post types and taxonomies
*/
static function get_disabled_post_types( $include_user_excluded = true ) {
global $wp_post_types, $permalink_manager_options;
$allowed_post_types = array(
'shop_coupon'
);
$disabled_post_types = array(
'revision',
'nav_menu_item',
'algolia_task',
'fl_builder',
'fl-builder',
'fl-builder-template',
'fl-theme-layout',
'fusion_tb_layout',
'fusion_tb_section',
'fusion_template',
'fusion_element',
'wc_product_tab',
'wc_voucher',
'wc_voucher_template',
'sliders',
'thirstylink',
'elementor_library',
'elementor_menu_item',
'cms_block',
'nooz_coverage',
'bricks_template'
);
// 1. Disable post types that are not publicly_queryable
foreach ( $wp_post_types as $post_type ) {
if ( in_array( $post_type->name, $allowed_post_types ) ) {
continue;
}
if ( ! is_post_type_viewable( $post_type ) || ( empty( $post_type->query_var ) && empty( $post_type->rewrite ) && empty( $post_type->_builtin ) && ! empty( $permalink_manager_options['general']['partial_disable_strict'] ) ) ) {
$disabled_post_types[] = $post_type->name;
}
}
// 2. Add post types disabled by user
if ( $include_user_excluded ) {
$disabled_post_types = ( ! empty( $permalink_manager_options['general']['partial_disable']['post_types'] ) ) ? array_merge( (array) $permalink_manager_options['general']['partial_disable']['post_types'], $disabled_post_types ) : $disabled_post_types;
}
return apply_filters( 'permalink_manager_disabled_post_types', $disabled_post_types );
}
/**
* Get the array of all (including/excluding user selected) disabled taxonomies
*
* @param bool $include_user_excluded
*
* @return array
*/
static function get_disabled_taxonomies( $include_user_excluded = true ) {
global $wp_taxonomies, $permalink_manager_options;
$disabled_taxonomies = array(
'product_shipping_class',
'post_status',
'fl-builder-template-category',
'post_format',
'nav_menu',
'language',
'template_bundle'
);
// 1. Disable taxonomies that are not publicly_queryable
foreach ( $wp_taxonomies as $taxonomy ) {
if ( ! is_taxonomy_viewable( $taxonomy ) || ( empty( $taxonomy->query_var ) && empty( $taxonomy->rewrite ) && empty( $taxonomy->_builtin ) && ! empty( $permalink_manager_options['general']['partial_disable_strict'] ) ) ) {
$disabled_taxonomies[] = $taxonomy->name;
}
}
// 2. Add taxonomies disabled by user
if ( $include_user_excluded ) {
$disabled_taxonomies = ( ! empty( $permalink_manager_options['general']['partial_disable']['taxonomies'] ) ) ? array_merge( (array) $permalink_manager_options['general']['partial_disable']['taxonomies'], $disabled_taxonomies ) : $disabled_taxonomies;
}
return apply_filters( 'permalink_manager_disabled_taxonomies', $disabled_taxonomies );
}
/**
* Check if the post type should be ignored by Permalink Manager
*
* @param string $post_type
* @param bool $check_if_exists
*
* @return bool
*/
static public function is_post_type_disabled( $post_type, $check_if_exists = true ) {
$disabled_post_types = self::get_disabled_post_types();
$post_type_exists = ( $check_if_exists ) ? post_type_exists( $post_type ) : true;
return ( ( is_array( $disabled_post_types ) && in_array( $post_type, $disabled_post_types ) ) || empty( $post_type_exists ) ) ? true : false;
}
/**
* Check if the taxonomy should be ignored by Permalink Manager
*
* @param string $taxonomy
* @param bool $check_if_exists
*
* @return bool
*/
static public function is_taxonomy_disabled( $taxonomy, $check_if_exists = true ) {
$disabled_taxonomies = self::get_disabled_taxonomies();
$taxonomy_exists = ( $check_if_exists ) ? taxonomy_exists( $taxonomy ) : true;
return ( ( is_array( $disabled_taxonomies ) && in_array( $taxonomy, $disabled_taxonomies ) ) || empty( $taxonomy_exists ) ) ? true : false;
}
/**
* Get a list of all excluded posts' IDs
*
* @return array
*/
public static function get_excluded_post_ids() {
global $permalink_manager_options;
if ( ! empty( $permalink_manager_options["general"]["exclude_post_ids"] ) ) {
$excluded_post_ids_raw = $permalink_manager_options["general"]["exclude_post_ids"];
$excluded_post_ids = self::get_ids_from_string( $excluded_post_ids_raw );
} else {
$excluded_post_ids = array();
}
// Allow to filter the array via filter
$excluded_post_ids = apply_filters( 'permalink_manager_excluded_post_ids', $excluded_post_ids );
// Only numeric IDs are allowed
return ( ! empty( $excluded_post_ids ) ) ? array_filter( $excluded_post_ids, 'is_numeric' ) : array();
}
/**
* Check if specific post should be ignored by Permalink Manager
*
* @param WP_Post|int $post
* @param bool $draft_check
* @param bool $strict_draft_check
*
* @return bool
*/
public static function is_post_excluded( $post = null, $draft_check = false, $strict_draft_check = false ) {
$post = ( is_numeric( $post ) ) ? get_post( $post ) : $post;
// 1. Check if post type is disabled
if ( ! empty( $post->post_type ) && self::is_post_type_disabled( $post->post_type ) ) {
return true;
}
// 2. Get list of post IDs excluded in the plugin settings or via the filter
$excluded_post_ids = self::get_excluded_post_ids();
// 3. Exclude post IDs excluded via the filter or in the plugin settings
if ( is_array( $excluded_post_ids ) && ! empty( $post->ID ) && in_array( $post->ID, $excluded_post_ids ) ) {
return true;
}
// D. Check if post is a "draft", "pending", removed
if ( $draft_check ) {
return self::is_draft_excluded( $post, $strict_draft_check );
}
return false;
}
/**
* Check if specific post is a draft (or pending)
*
* @param WP_Post|int $post
* @param bool $strict_draft_check
*
* @return bool
*/
public static function is_draft_excluded( $post = null, $strict_draft_check = false ) {
global $permalink_manager_options;
$post = ( is_numeric( $post ) ) ? get_post( $post ) : $post;
// Check if post is a "draft", "pending" or moved to trash
if ( ! empty( $post->post_status ) ) {
if ( ! empty( $permalink_manager_options["general"]["ignore_drafts"] ) || $strict_draft_check ) {
$post_statuses = ( $permalink_manager_options["general"]["ignore_drafts"] == 2 ) ? array( 'draft', 'pending' ) : array( 'draft' );
if ( in_array( $post->post_status, $post_statuses ) ) {
return true;
}
}
if ( in_array( $post->post_status, array( 'auto-draft', 'trash' ) ) || ( strpos( $post->post_name, 'revision-v1' ) !== false ) || ( ! empty( $post->post_name ) && $post->post_name == 'auto-draft' ) ) {
return true;
}
}
return false;
}
/**
* Get a list of all excluded terms' IDs
*
* @return array
*/
public static function get_excluded_term_ids() {
global $permalink_manager_options;
if ( ! empty( $permalink_manager_options["general"]["exclude_term_ids"] ) ) {
$excluded_term_ids_raw = $permalink_manager_options["general"]["exclude_term_ids"];
$excluded_term_ids = self::get_ids_from_string( $excluded_term_ids_raw );
} else {
$excluded_term_ids = array();
}
// Allow to filter the array via filter
$excluded_term_ids = apply_filters( 'permalink_manager_excluded_term_ids', $excluded_term_ids );
// Only numeric IDs are allowed
return ( ! empty( $excluded_term_ids ) ) ? array_filter( $excluded_term_ids, 'is_numeric' ) : array();
}
/**
* Check if specific term should be ignored by Permalink Manager
*
* @param WP_Term $term
*
* @return bool
*/
public static function is_term_excluded( $term = null ) {
$term = ( is_numeric( $term ) ) ? get_term( $term ) : $term;
// 1. Check if taxonomy is disabled
if ( ! empty( $term->taxonomy ) && self::is_taxonomy_disabled( $term->taxonomy ) ) {
return true;
}
// 2. Get list of term IDs excluded in the plugin settings or via the filter
$excluded_term_ids = self::get_excluded_term_ids();
// 3. Exclude term IDs excluded via the filter or in the plugin settings
if ( is_array( $excluded_term_ids ) && ! empty( $term->term_id ) && in_array( $term->term_id, $excluded_term_ids ) ) {
return true;
}
return false;
}
/**
* Get all post types supported by Permalink Manager
*
* @param string $format
* @param string $cpt
* @param bool $include_user_excluded
*
* @return array
*/
static function get_post_types_array( $format = null, $cpt = null, $include_user_excluded = false ) {
global $wp_post_types;
$post_types_array = array();
$disabled_post_types = self::get_disabled_post_types( ! $include_user_excluded );
foreach ( $wp_post_types as $post_type ) {
if ( $format == 'full' ) {
$post_types_array[ $post_type->name ] = array( 'label' => $post_type->labels->name, 'name' => $post_type->name );
} else if ( $format == 'archive_slug' ) {
// Ignore non-public post types
if ( ! is_post_type_viewable( $post_type ) || empty( $post_type->has_archive ) ) {
continue;
}
if ( ! $post_type->has_archive ) {
$archive_slug = $post_type->has_archive;
} else if ( is_array( $post_type->rewrite ) && ! empty( $post_type->rewrite['slug'] ) ) {
$archive_slug = $post_type->rewrite['slug'];
} else {
$archive_slug = $post_type->name;
}
$post_types_array[ $post_type->name ] = $archive_slug;
} else {
$post_types_array[ $post_type->name ] = $post_type->labels->name;
}
}
if ( is_array( $disabled_post_types ) ) {
foreach ( $disabled_post_types as $post_type ) {
if ( ! empty( $post_types_array[ $post_type ] ) ) {
unset( $post_types_array[ $post_type ] );
}
}
}
return ( empty( $cpt ) ) ? $post_types_array : $post_types_array[ $cpt ];
}
/**
* Get all taxonomies supported by Permalink Manager
*
* @param string $format
* @param string $tax
* @param bool $include_user_excluded
*
* @return array
*/
static function get_taxonomies_array( $format = null, $tax = null, $include_user_excluded = false ) {
global $wp_taxonomies;
$taxonomies_array = array();
$disabled_taxonomies = self::get_disabled_taxonomies( ! $include_user_excluded );
foreach ( $wp_taxonomies as $taxonomy ) {
$taxonomy_name = ( ! empty( $taxonomy->labels->name ) ) ? $taxonomy->labels->name : '-';
$taxonomies_array[ $taxonomy->name ] = ( $format == 'full' ) ? array( 'label' => $taxonomy->labels->name, 'name' => $taxonomy->name ) : $taxonomy_name;
}
if ( is_array( $disabled_taxonomies ) ) {
foreach ( $disabled_taxonomies as $taxonomy ) {
if ( ! empty( $taxonomies_array[ $taxonomy ] ) ) {
unset( $taxonomies_array[ $taxonomy ] );
}
}
}
ksort( $taxonomies_array );
return ( empty( $tax ) ) ? $taxonomies_array : $taxonomies_array[ $tax ];
}
/**
* Get all post statuses supported by Permalink Manager
*/
static function get_post_statuses() {
$post_statuses = get_post_statuses();
if ( is_array( $post_statuses ) ) {
$post_statuses['future'] = __( 'Scheduled', 'permalink-manager' );
}
return apply_filters( 'permalink_manager_post_statuses', $post_statuses );
}
/**
* Get all the endpoints registered for WP_Rewrite object
*/
static function get_endpoints() {
global $wp_rewrite;
$pagination_endpoint = ( ! empty( $wp_rewrite->pagination_base ) ) ? $wp_rewrite->pagination_base : 'page';
// Start with default endpoints
$endpoints = "{$pagination_endpoint}|feed|embed|attachment|trackback";
if ( ! empty( $wp_rewrite->endpoints ) ) {
foreach ( $wp_rewrite->endpoints as $endpoint ) {
$endpoints .= "|{$endpoint[1]}";
}
}
return apply_filters( "permalink_manager_endpoints", str_replace( "/", "\/", $endpoints ) );
}
/**
* Check if the specific post is selected as a front-page
*
* @param int $page_id
*
* @return bool
*/
static function is_front_page( $page_id ) {
$front_page_id = get_option( 'page_on_front' );
$bool = ( ! empty( $front_page_id ) && $page_id == $front_page_id ) ? true : false;
return apply_filters( 'permalink_manager_is_front_page', $bool, $page_id, $front_page_id );
}
/**
* Check if the advanced mode is turned on
*
* @return bool
*/
static function is_advanced_mode_on() {
global $permalink_manager_options;
$bool = ( ! empty( $permalink_manager_options["general"]["advanced_mode"] ) && $permalink_manager_options["general"]["advanced_mode"] == 1 ) ? true : false;
return apply_filters( 'permalink_manager_advanced_mode_on', $bool );
}
/**
* Sanitize the multidimensional array
*
* @param array $data
*
* @return array
*/
static function sanitize_array( $data = array(), $sanitize = false ) {
if ( ! is_array( $data ) || ! count( $data ) ) {
return array();
}
foreach ( $data as $k => $v ) {
if ( ! is_array( $v ) && ! is_object( $v ) ) {
$data[ $k ] = ( $sanitize ) ? sanitize_text_field( trim( $v ) ) : htmlspecialchars( trim( $v ) );
}
if ( is_array( $v ) ) {
$data[ $k ] = self::sanitize_array( $v );
}
}
return $data;
}
/**
* Prepare an array of strings for use in SQL IN clause
*
* @param array $array Array of strings to prepare
* @param bool $sanitize
*
* @return string Comma-separated string with quoted values, e.g., "'value1','value2','value3'"
*/
static function prepare_array_for_sql_in( $array, $sanitize = true ) {
// Ensure we have an array
if ( ! is_array( $array ) ) {
$array = (array) $array;
}
// Remove empty values and sanitize
$array = array_filter( $array );
$array = ( $sanitize ) ? array_map( 'sanitize_text_field', $array ) : $array;
// Return empty string if array is empty
if ( empty( $array ) ) {
return '';
}
// Escape and wrap each value with quotes
$escaped = array_map( function ( $value ) {
return "'" . esc_sql( $value ) . "'";
}, $array );
// Join with comma
return implode( ',', $escaped );
}
/**
* Convert the text containing IDs and/or ID ranges to an array
*
* @param string $data
*
* @return array
*/
static function get_ids_from_string( $data ) {
// Remove whitespaces and other invalid characters
$raw_ids = esc_sql( preg_replace( '/[^\d,-]/', '', $data ) );
// Convert the string into the array with IDs and/or ranges
preg_match_all( "/([\d]+(?:-?[\d]+)?)/x", $raw_ids, $groups );
$ids = array();
if ( ! empty( $groups[1] ) ) {
foreach ( $groups[1] as $group ) {
if ( is_numeric( $group ) ) {
$ids[] = $group;
} else if ( preg_match( '/([\d]+)-([\d]+)/', $group, $range_limits ) ) {
$range_start = (int) $range_limits[1];
$range_end = (int) $range_limits[2];
if ( $range_start < $range_end ) {
$ids = array_merge( $ids, range( $range_start, $range_end ) );
}
}
}
$ids = array_unique( $ids );
}
return $ids;
}
/**
* Encode URI and keep special characters
*
* @param string $uri
*
* @return string
*/
static function encode_uri( $uri ) {
return str_replace( array( '%2F', '%2C', '%7C', '%2B' ), array( '/', ',', '|', '+' ), urlencode( $uri ) );
}
/**
* Sanitize the given string to URI-safe format
*
* @param string $str
* @param bool $keep_percent_sign
* @param bool $force_lowercase
* @param bool $sanitize_slugs
*
* @return string
*/
public static function sanitize_title( $str, $keep_percent_sign = false, $force_lowercase = null, $sanitize_slugs = null ) {
global $permalink_manager_options;
// Force lowercase & hyphens
$force_lowercase = ( ! is_null( $force_lowercase ) ) ? $force_lowercase : apply_filters( 'permalink_manager_force_lowercase_uris', true );
if ( is_null( $sanitize_slugs ) ) {
$sanitize_slugs = ( ! empty( $permalink_manager_options['general']['disable_slug_sanitization'] ) ) ? false : true;
}
// Allow to filter the slug before it is sanitized
$str = apply_filters( 'permalink_manager_pre_sanitize_title', $str, $keep_percent_sign, $force_lowercase, $sanitize_slugs );
// Remove accents & entities
$clean = ( empty( $permalink_manager_options['general']['keep_accents'] ) ) ? remove_accents( $str ) : $str;
$clean = str_replace( array( '&lt', '&gt', '&amp' ), '', $clean );
$percent_sign = ( $keep_percent_sign ) ? "\%" : "";
$sanitize_regex = apply_filters( "permalink_manager_sanitize_regex", "/[^\p{Xan}a-zA-Z0-9{$percent_sign}\/_\.|+, -]/ui", $percent_sign );
$clean = preg_replace( $sanitize_regex, '', $clean );
if ( empty( $clean ) ) {
return $str;
}
// Lowercase with non-ASCII characters support
if ( $force_lowercase && function_exists( 'mb_strtolower' ) && function_exists( 'wp_is_valid_utf8' ) && wp_is_valid_utf8( $clean ) ) {
$clean = mb_strtolower( $clean, 'UTF-8' );
} else if ( $force_lowercase ) {
$clean = strtolower( $clean );
}
// Remove ampersand
$clean = str_replace( array( '%26', '&' ), '', $clean );
// Remove special characters
if ( $sanitize_slugs !== false ) {
$clean = preg_replace( "/[\s|+-]+/", "-", $clean );
$clean = preg_replace( "/[,]+/", "", $clean );
$clean = preg_replace( '/([\.]+)(?![a-z]{3,4}$)/i', '', $clean );
$clean = preg_replace( '/([-\s+]\/[-\s+])/', '-', $clean );
$clean = preg_replace( '|-+|', '-', $clean );
} else {
$clean = preg_replace( "/[\s]+/", "-", $clean );
}
// Remove widow & duplicated slashes
$clean = preg_replace( '/([-]*[\/]+[-]*)/', '/', $clean );
$clean = preg_replace( '/([\/]+)/', '/', $clean );
// Trim slashes, dashes and whitespaces
$clean = trim( $clean, " /-" );
// Allow to filter the slug after it is sanitized
return apply_filters( 'permalink_manager_sanitize_title', $clean, $str, $keep_percent_sign, $force_lowercase, $sanitize_slugs );
}
/**
* Replace the string in custom permalink or native slug
*
* @param string $old_string
* @param string $new_string
* @param string $old_slug
* @param string $old_uri
* @param string $mode
*
* @return array|void
*/
public static function replace_uri_slug( $old_string, $new_string, $old_slug, $old_uri, $mode ) {
if ( preg_match( "/^\#.+\#$/i", $old_string ) ) {
$old_string = trim( $old_string, '#' );
$allowed_in_chars = '/[\w\d\s\.\*\+\-\?\!\/\|\(\)\{\}\[\]^$]/';
$allowed_out_chars = '/^[a-zA-Z0-9\.\/\$\-\_]+$/';
if ( ! preg_match( $allowed_in_chars, $old_string ) || ! preg_match( $allowed_out_chars, $new_string ) ) {
die( 'Invalid characters in REGEX formula.' );
}
$pattern = "#{$old_string}#";
$new_slug = ( $mode == 'slugs' ) ? preg_replace( $pattern, $new_string, $old_slug ) : $old_slug;
$new_uri = ( $mode != 'slugs' ) ? preg_replace( $pattern, $new_string, $old_uri ) : $old_uri;
} else {
$new_slug = ( $mode == 'slugs' ) ? str_replace( $old_string, $new_string, $old_slug ) : $old_slug;
$new_uri = ( $mode != 'slugs' ) ? str_replace( $old_string, $new_string, $old_uri ) : $old_uri;
}
return array( $new_slug, $new_uri );
}
/**
* Sanitize the final custom permalink URI
*
* @param string $uri
*
* @return string
*/
public static function clear_single_uri( $uri ) {
return self::sanitize_title( $uri, true );
}
/**
* Remove all slashes from given string
*
* @param string $uri
* @param string $replacement
*
* @return array|string|string[]|null
*/
public static function remove_slashes( $uri, $replacement = '' ) {
return preg_replace( "/[\/]+/", $replacement, $uri );
}
/**
* Replace the given slug with the actual title or custom permalink of specific post or term
*
* @param string $slug
* @param WP_Post|WP_Term $object
* @param bool $flat
* @param null $force_custom_slugs
* @param bool $allow_inherit
*
* @return string
*/
public static function force_custom_slugs( $slug, $object, $flat = false, $force_custom_slugs = null, $allow_inherit = true ) {
global $permalink_manager_options;
if ( empty( $force_custom_slugs ) ) {
$force_custom_slugs = ( ! empty( $permalink_manager_options['general']['force_custom_slugs'] ) ) ? $permalink_manager_options['general']['force_custom_slugs'] : false;
$force_custom_slugs = apply_filters( 'permalink_manager_force_custom_slugs', $force_custom_slugs, $slug, $object );
}
if ( $force_custom_slugs ) {
// A. Custom slug (title)
if ( $force_custom_slugs == 1 ) {
if ( ! empty( $object->name ) && ! empty( $object->taxonomy ) ) {
$title = $object->name;
} else if ( ! empty( $object->post_title ) && ! empty( $object->post_type ) ) {
$title = $object->post_title;
} else {
return $slug;
}
$title = wp_strip_all_tags( $title );
$title = self::remove_slashes( $title, '-' );
$new_slug = self::sanitize_title( $title );
} // B. Custom slug (inherit custom permalink from the parent object)
else if ( $allow_inherit ) {
$new_slug = basename( Permalink_Manager_URI_Functions::get_single_uri( $object, false, true ) );
}
$slug = ( ! empty( $new_slug ) ) ? preg_replace( '/([^\/]+)$/', $new_slug, $slug ) : $slug;
}
if ( $flat ) {
$slug = preg_replace( "/([^\/]+)(.*)/", "$1", $slug );
}
return $slug;
}
/**
* Ensures that a permalink is unique by appending a numeric suffix if needed
*
* @param string $new_uri
* @param string|int $element_id
* @param string|WP_Post|WP_Term $element
*
* @return string A unique custom permalink string.
*/
function force_unique_uri( $new_uri, $element_id, $element = '' ) {
// Determine the ID based on the object type
if ( is_a( $element, 'WP_Term' ) && strpos( current_action(), "_term_" ) !== false ) {
$element_id = $element->term_id;
$is_tax = true;
} elseif ( is_a( $element, 'WP_Post' ) ) {
$element_id = $element->ID;
}
if ( empty( $element_id ) || ! is_numeric( $element_id ) ) {
return $new_uri;
}
if ( ! empty( $is_tax ) ) {
$element_id = "tax-{$element_id}";
}
// Prepare base + extension
if ( ! preg_match( '/^(.+?)(?:-([\d]+))?(\.[^\.]+$|$)/', $new_uri, $parts ) ) {
return $new_uri; // fallback if regex fails
}
$base = $parts[1];
$index = empty( $parts[2] ) ? 1 : (int) $parts[2];
$extension = $parts[3];
$unique_uri = $new_uri;
// Increment suffix until URI is unique
while ( Permalink_Manager_URI_Functions::is_uri_duplicated( $unique_uri, $element_id ) ) {
$index ++;
$unique_uri = "{$base}-{$index}{$extension}";
}
return $unique_uri;
}
/**
* Reload the globals when the blog is switched (multisite)
*
* @param int $new_blog_id
*/
public function reload_globals_in_network( $new_blog_id ) {
global $permalink_manager_uris, $permalink_manager_redirects, $permalink_manager_external_redirects;
if ( function_exists( 'get_blog_option' ) ) {
$permalink_manager_uris = get_blog_option( $new_blog_id, 'permalink-manager-uris', array() );
$permalink_manager_redirects = get_blog_option( $new_blog_id, 'permalink-manager-redirects', array() );
$permalink_manager_external_redirects = get_blog_option( $new_blog_id, 'permalink-manager-external-redirects', array() );
}
}
}
@@ -0,0 +1,159 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Helper functions used for Permastructures (permalink formats)
*/
class Permalink_Manager_Permastructure_Functions {
public function __construct() {
add_action( 'plugins_loaded', array( $this, 'init' ), 5 );
}
/**
* Add hooks used by plugin to filter the custom permalinks
*/
function init() {
// Replace empty placeholder tags & remove BOM
add_filter( 'permalink_manager_filter_default_post_uri', array( $this, 'replace_empty_placeholder_tags' ), 10, 5 );
add_filter( 'permalink_manager_filter_default_term_uri', array( $this, 'replace_empty_placeholder_tags' ), 10, 5 );
}
/**
* Get the default permalink format for specific post type
*
* @param string $post_type
* @param bool $remove_post_tag
*
* @return string
*/
static function get_default_permastruct( $post_type = 'page', $remove_post_tag = false ) {
global $wp_rewrite;
// Get default permastruct
if ( $post_type == 'page' ) {
$permastruct = $wp_rewrite->get_page_permastruct();
} else if ( $post_type == 'post' ) {
$permastruct = get_option( 'permalink_structure' );
} else {
$permastruct = $wp_rewrite->get_extra_permastruct( $post_type );
}
return ( $remove_post_tag ) ? trim( str_replace( array( "%postname%", "%pagename%", "%{$post_type}%" ), "", $permastruct ), "/" ) : $permastruct;
}
/**
* Get the post name permastructure tag for specific post type
*
* @param string $post_type
*
* @return string
*/
static function get_post_tag( $post_type ) {
// Get the post type (with fix for posts & pages)
if ( $post_type == 'page' ) {
$post_type_tag = '%pagename%';
} else if ( $post_type == 'post' ) {
$post_type_tag = '%postname%';
} else {
$post_type_tag = "%{$post_type}%";
}
return $post_type_tag;
}
/**
* Check if any of slug tags is present inside Permastructure settings
*
* @param $default_uri
* @param $slug_tags
* @param $content_element
*
* @return bool|null
*/
public static function is_slug_tag_present( $default_uri, $slug_tags, $content_element ) {
global $permalink_manager_options;
// Check if any post tag is present in custom permastructure
if ( ! empty( $content_element->post_type ) ) {
$content_type = $content_element->post_type;
$content_type_key = 'post_types';
} else if ( ! empty( $content_element->taxonomy ) ) {
$content_type = $content_element->taxonomy;
$content_type_key = 'taxonomies';
} else {
return null;
}
$permastructure_settings = ( ! empty( $permalink_manager_options['permastructure-settings'] ) ) ? $permalink_manager_options['permastructure-settings'] : array();
$do_not_append_settings = ( ! empty( $permastructure_settings['do_not_append_slug'] ) ) ? $permastructure_settings['do_not_append_slug'] : array();
$do_not_append_slug = ( ! empty( $do_not_append_settings[ $content_type_key ] ) && ! empty( $do_not_append_settings[ $content_type_key ][ $content_type ] ) ) ? true : false;
$do_not_append_slug = apply_filters( "permalink_manager_do_not_append_slug", $do_not_append_slug, $content_type, $content_element );
if ( ! $do_not_append_slug ) {
foreach ( $slug_tags as $tag ) {
if ( strpos( $default_uri, $tag ) !== false ) {
$do_not_append_slug = true;
break;
}
}
}
// 3F. Replace the post tags with slugs or append the slug if no post tag is defined
if ( ! empty( $do_not_append_slug ) ) {
return true;
} else {
return false;
}
}
/**
* Replace empty placeholder tags & remove BOM
*
* @param string $default_uri
* @param string $native_slug
* @param string $element
* @param string $slug
* @param bool $native_uri
*
* @return string
*/
public static function replace_empty_placeholder_tags( $default_uri, $native_slug = "", $element = "", $slug = "", $native_uri = false ) {
// Remove the BOM
$default_uri = str_replace( array( "\xEF\xBB\xBF", "%ef%bb%bf" ), '', $default_uri );
// Encode the URI before placeholders are removed
$chunks = explode( '/', $default_uri );
foreach ( $chunks as &$chunk ) {
if ( ! preg_match( "/^(%.+?%)$/", $chunk ) && preg_match( '/%[A-F0-9]{2}%[A-F0-9]{2}/i', $chunk ) ) {
$chunk = rawurldecode( $chunk );
}
}
$default_uri = implode( "/", $chunks );
$empty_tag_replacement = apply_filters( 'permalink_manager_empty_tag_replacement', '', $element );
$default_uri = preg_replace( "/%(.+?)%/", $empty_tag_replacement, $default_uri );
$default_uri = str_replace( "//", "/", $default_uri );
return trim( $default_uri, "/" );
}
/**
* Get the permalink base (home URL) for custom permalink
*
* @param string|int|WP_Post|WP_Term $element
*
* @return string
*/
public static function get_permalink_base( $element = null ) {
$home_url = trim( get_option( 'home' ), "/" );
// Make sure that the custom permalinks have a valid scheme
if ( strpos( $home_url, 'http' ) === false ) {
$home_url = set_url_scheme( $home_url );
}
return apply_filters( 'permalink_manager_filter_permalink_base', $home_url, $element );
}
}
@@ -0,0 +1,485 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Additional hooks for "Permalink Manager Pro"
*/
class Permalink_Manager_Pro_Functions {
public function __construct() {
// Stop words
add_filter( 'permalink_manager_filter_default_post_uri', array( $this, 'remove_stop_words' ), 9, 3 );
add_filter( 'permalink_manager_filter_default_term_uri', array( $this, 'remove_stop_words' ), 9, 3 );
// Custom fields in permalinks
add_filter( 'permalink_manager_filter_default_post_uri', array( $this, 'replace_custom_field_tags' ), 9, 5 );
add_filter( 'permalink_manager_filter_default_term_uri', array( $this, 'replace_custom_field_tags' ), 9, 5 );
// Save redirects
add_action( 'permalink_manager_updated_post_uri', array( $this, 'save_redirects' ), 9, 5 );
add_action( 'permalink_manager_updated_term_uri', array( $this, 'save_redirects' ), 9, 5 );
}
/**
* Get the list of languages where stop words are defined
*/
static function load_stop_words_languages() {
return array(
'ar' => __( 'Arabic', 'permalink-manager' ),
'zh' => __( 'Chinese', 'permalink-manager' ),
'da' => __( 'Danish', 'permalink-manager' ),
'nl' => __( 'Dutch', 'permalink-manager' ),
'en' => __( 'English', 'permalink-manager' ),
'fi' => __( 'Finnish', 'permalink-manager' ),
'fr' => __( 'French', 'permalink-manager' ),
'de' => __( 'German', 'permalink-manager' ),
'he' => __( 'Hebrew', 'permalink-manager' ),
'hi' => __( 'Hindi', 'permalink-manager' ),
'it' => __( 'Italian', 'permalink-manager' ),
'ja' => __( 'Japanese', 'permalink-manager' ),
'ko' => __( 'Korean', 'permalink-manager' ),
'no' => __( 'Norwegian', 'permalink-manager' ),
'fa' => __( 'Persian', 'permalink-manager' ),
'pl' => __( 'Polish', 'permalink-manager' ),
'pt' => __( 'Portuguese', 'permalink-manager' ),
'ru' => __( 'Russian', 'permalink-manager' ),
'es' => __( 'Spanish', 'permalink-manager' ),
'sv' => __( 'Swedish', 'permalink-manager' ),
'tr' => __( 'Turkish', 'permalink-manager' )
);
}
/**
* Load stop words for specific language (using ISO code)
*
* @param string $iso
*
* @return array
*/
static function load_stop_words( $iso = '' ) {
$json_dir = PERMALINK_MANAGER_DIR . "/includes/vendor/stopwords-json/dist/{$iso}.json";
$json_a = array();
if ( file_exists( $json_dir ) ) {
$string = file_get_contents( $json_dir );
$json_a = json_decode( $string, true );
}
return $json_a;
}
/**
* Remove the stop words from the default custom permalinks
*
* @param string $slug The slug that is being generated.
* @param stdClass $object The object that the slug is being generated for.
* @param string $name The name of the filter.
*
* @return string The slug.
*/
public function remove_stop_words( $slug, $object, $name ) {
global $permalink_manager_options;
if ( ! empty( $permalink_manager_options['stop-words']['stop-words-enable'] ) && ! empty( $permalink_manager_options['stop-words']['stop-words-list'] ) ) {
$stop_words = explode( ",", strtolower( stripslashes( $permalink_manager_options['stop-words']['stop-words-list'] ) ) );
foreach ( $stop_words as $stop_word ) {
$stop_word = trim( $stop_word );
$slug = preg_replace( "/([\/-]|^)({$stop_word})([\/-]|$)/", '$1$3', $slug );
}
// Clear the slug
$slug = preg_replace( "/(-+)/", "-", trim( $slug, "-" ) );
$slug = preg_replace( "/(-\/-)|(\/-)|(-\/)/", "/", $slug );
}
return $slug;
}
/**
* Replace custom field tags in the default custom permalinks
*
* @param string $default_uri
* @param string $native_slug
* @param WP_Post|WP_Term $element
* @param string $slug
* @param string $native_uri
*
* @return string
*/
function replace_custom_field_tags( $default_uri, $native_slug, $element, $slug, $native_uri ) {
// Do not affect native URIs
if ( $native_uri ) {
return $default_uri;
}
preg_match_all( "/%__(.[^\%]+)%/", $default_uri, $custom_fields );
if ( ! empty( $custom_fields[1] ) ) {
foreach ( $custom_fields[1] as $i => $custom_field ) {
// Reset custom field value
$custom_field_value = "";
// Check additional arguments (__custom_field.argument_value)
if ( strpos( $custom_field, '.' ) !== false ) {
$custom_field_split = preg_split( '/[\.]/', $custom_field );
if ( ! empty( $custom_field_split[1] ) ) {
$custom_field_arg = $custom_field_split[1];
$custom_field = $custom_field_split[0];
}
}
// 1. Use WooCommerce fields (SKU)
if ( class_exists( 'WooCommerce' ) && strtolower( $custom_field ) == 'sku' && ! empty( $element->ID ) ) {
$product = wc_get_product( $element->ID );
$custom_field_value = ( is_a( $product, 'WC_Product' ) ) ? $product->get_sku() : '';
} // 2. Try to get value using ACF API
else if ( function_exists( 'get_field_object' ) ) {
$acf_element_id = ( ! empty( $element->ID ) ) ? $element->ID : "{$element->taxonomy}_{$element->term_id}";
$field_object = get_field_object( $custom_field, $acf_element_id );
// A. Relationship field
if ( ! empty( $field_object['type'] ) && ( in_array( $field_object['type'], array( 'relationship', 'post_object', 'taxonomy' ) ) ) && ! empty( $field_object['value'] ) ) {
$rel_elements = $field_object['value'];
// B1. Terms
if ( $field_object['type'] == 'taxonomy' ) {
if ( ( is_array( $rel_elements ) ) ) {
if ( is_numeric( $rel_elements[0] ) && ! empty( $field_object['taxonomy'] ) ) {
$rel_elements = get_terms( array( 'include' => $rel_elements, 'taxonomy' => $field_object['taxonomy'], 'hide_empty' => false, 'orderby' => 'term_id', 'order' => 'DESC' ) );
}
// Get the lowest term
if ( ! is_wp_error( $rel_elements ) && ! empty( $rel_elements ) && is_object( $rel_elements[0] ) ) {
$rel_term = Permalink_Manager_Helper_Functions::get_lowest_element( $rel_elements[0], $rel_elements );
}
}
if ( ! empty( $rel_elements->term_id ) ) {
$rel_term = $rel_elements;
} else if ( ! empty( $rel_elements ) && is_numeric( $rel_elements ) ) {
$rel_term = get_term( $rel_elements, $field_object['taxonomy'] );
}
// Get the replacement slug
if ( ! empty( $rel_term->term_id ) ) {
if ( ! empty( $custom_field_arg ) && is_numeric( $custom_field_arg ) ) {
$custom_field_value = Permalink_Manager_Helper_Functions::force_custom_slugs( $rel_term->slug, $rel_term, false, $custom_field_arg );
} else if ( ! empty( $custom_field_arg ) && $custom_field_arg == 'id' ) {
$custom_field_value = $rel_term->term_id;
} else if ( ! empty( $custom_field_arg ) && $custom_field_arg == 'full_slug' ) {
$custom_field_value = Permalink_Manager_Helper_Functions::get_term_full_slug( $rel_term, $rel_elements );
} else if ( ! empty( $custom_field_arg ) && $custom_field_arg == 'custom_permalink' ) {
$custom_field_value = Permalink_Manager_URI_Functions::get_single_uri( $rel_term );
} else {
$custom_field_value = Permalink_Manager_Helper_Functions::force_custom_slugs( $rel_term->slug, $rel_term );
}
}
} // B2. Posts
else {
if ( ( is_array( $rel_elements ) ) ) {
if ( is_numeric( $rel_elements[0] ) ) {
$rel_elements = get_posts( array( 'include' => $rel_elements, 'post_type' => 'any', 'orderby' => 'post__in' ) );
}
// Get lowest element
if ( ! is_wp_error( $rel_elements ) && ! empty( $rel_elements ) && is_object( $rel_elements[0] ) ) {
$rel_post = Permalink_Manager_Helper_Functions::get_lowest_element( $rel_elements[0], $rel_elements );
}
} else if ( ! empty( $rel_elements->ID ) ) {
$rel_post = $rel_elements;
}
if ( ! empty( $rel_post->ID ) ) {
$rel_post_object = $rel_post;
} else if ( is_numeric( $rel_elements ) ) {
$rel_post_object = get_post( $rel_elements );
}
// Get the replacement slug
if ( ! empty( $rel_post_object->ID ) ) {
if ( ! empty( $custom_field_arg ) && is_numeric( $custom_field_arg ) ) {
$custom_field_value = Permalink_Manager_Helper_Functions::force_custom_slugs( $rel_post_object->post_name, $rel_post_object, false, $custom_field_arg );
} else if ( ! empty( $custom_field_arg ) && $custom_field_arg == 'id' ) {
$custom_field_value = $rel_post_object->ID;
} else if ( ! empty( $custom_field_arg ) && $custom_field_arg == 'full_slug' ) {
$custom_field_value = Permalink_Manager_Helper_Functions::get_post_full_slug( $rel_post_object );
} else if ( ! empty( $custom_field_arg ) && $custom_field_arg == 'custom_permalink' ) {
$custom_field_value = Permalink_Manager_URI_Functions::get_single_uri( $rel_post_object );
} else {
$custom_field_value = Permalink_Manager_Helper_Functions::force_custom_slugs( $rel_post_object->post_name, $rel_post_object );
}
}
}
} // C. Text field
else {
$custom_field_value = ( ! empty( $field_object['value'] ) ) ? $field_object['value'] : "";
$custom_field_value = ( ! empty( $custom_field_value['value'] ) ) ? $custom_field_value['value'] : $custom_field_value;
}
}
// 3. Use native method
if ( empty( $custom_field_value ) ) {
if ( ! empty( $element->ID ) ) {
$custom_field_value = get_post_meta( $element->ID, $custom_field, true );
// Toolset
if ( empty( $custom_field_value ) && ( defined( 'TYPES_VERSION' ) || defined( 'WPCF_VERSION' ) ) ) {
$custom_field_value = get_post_meta( $element->ID, "wpcf-{$custom_field}", true );
}
} else if ( ! empty( $element->term_id ) ) {
$custom_field_value = get_term_meta( $element->term_id, $custom_field, true );
} else {
$custom_field_value = "";
}
}
// Allow to filter the custom field value
$custom_field_value = apply_filters( 'permalink_manager_custom_field_value', $custom_field_value, $custom_field, $element );
// Make sure that custom field is a string
if ( ! empty( $custom_field_value ) && is_string( $custom_field_value ) ) {
// Do not sanitize the custom field if 'no-sanitize' argument is added (e.g. %__custom-field-name.no-sanitize%)
if ( empty( $custom_field_arg ) || strpos( $custom_field_arg, 'no-sanitize' ) === false ) {
$custom_field_value = Permalink_Manager_Helper_Functions::sanitize_title( $custom_field_value );
}
$default_uri = str_replace( $custom_fields[0][ $i ], $custom_field_value, $default_uri );
}
}
}
return $default_uri;
}
/**
* Save the custom, external redirects for specific post or term
*
* @param int|string $element_id The ID of the post (e.g. 123) or term (tax-456).
* @param string $new_uri The new custom permalink
* @param string $old_uri The previous custom permalink
* @param string $native_uri The native permalink
* @param string $default_uri The default custom permalink
*/
public function save_redirects( $element_id, $new_uri, $old_uri, $native_uri = '', $default_uri = '' ) {
global $permalink_manager_options, $permalink_manager_redirects;
// Do not trigger if "Extra redirects" option is turned off
if ( empty( $permalink_manager_options['general']['redirect'] ) || empty( $permalink_manager_options['general']['extra_redirects'] ) ) {
return;
}
// Terms IDs should be prepended with prefix
$element_id = ( current_filter() == 'permalink_manager_updated_term_uri' ) ? "tax-{$element_id}" : $element_id;
// Make sure that $permalink_manager_redirects variable is an array
$permalink_manager_redirects = ( is_array( $permalink_manager_redirects ) ) ? $permalink_manager_redirects : array();
// 1A. Post/term is saved or updated
if ( isset( $_POST['permalink-manager-redirects'] ) && is_array( $_POST['permalink-manager-redirects'] ) ) {
$permalink_manager_redirects[ $element_id ] = array_filter( $_POST['permalink-manager-redirects'] );
$redirects_updated = true;
} // 1B. All redirects are removed
else if ( isset( $_POST['permalink-manager-redirects'] ) ) {
$permalink_manager_redirects[ $element_id ] = array();
$redirects_updated = true;
}
// 1C. No longer needed
if ( isset( $_POST['permalink-manager-redirects'] ) ) {
unset( $_POST['permalink-manager-redirects'] );
}
// 2. Custom URI is updated
if ( get_option( 'page_on_front' ) !== $element_id && ! empty( $permalink_manager_options['general']['setup_redirects'] ) && ( $new_uri !== $old_uri ) ) {
// Make sure that the array with redirects exists
$permalink_manager_redirects[ $element_id ] = ( ! empty( $permalink_manager_redirects[ $element_id ] ) ) ? $permalink_manager_redirects[ $element_id ] : array();
// Append the old custom URI
$permalink_manager_redirects[ $element_id ][] = $old_uri;
$redirects_updated = true;
}
// 3. Save the custom redirects
if ( ! empty( $redirects_updated ) && is_array( $permalink_manager_redirects[ $element_id ] ) ) {
// Remove empty redirects
$permalink_manager_redirects[ $element_id ] = array_filter( $permalink_manager_redirects[ $element_id ] );
// Sanitize the array with redirects
foreach ( $permalink_manager_redirects[ $element_id ] as $i => $redirect ) {
$redirect = rawurldecode( $redirect );
$redirect = Permalink_Manager_Helper_Functions::sanitize_title( $redirect, true );
$permalink_manager_redirects[ $element_id ][ $i ] = $redirect;
}
// Reset the keys
$permalink_manager_redirects[ $element_id ] = array_values( $permalink_manager_redirects[ $element_id ] );
// Remove the duplicates
$permalink_manager_redirects[ $element_id ] = array_unique( $permalink_manager_redirects[ $element_id ] );
Permalink_Manager_Actions::clear_single_element_duplicated_redirect( $element_id, false, $new_uri );
// Remove empty subarray
$permalink_manager_redirects = array_filter( $permalink_manager_redirects );
update_option( 'permalink-manager-redirects', $permalink_manager_redirects );
}
// 4. Save the external redirect
if ( isset( $_POST['permalink-manager-external-redirect'] ) ) {
self::save_external_redirect( $_POST['permalink-manager-external-redirect'], $element_id );
}
}
/**
* Save external redirect
*
* @param string $url The full URL saved as external redirect
* @param string|int $element_id The ID of the post (e.g. 123) or term (tax-456).
*/
public static function save_external_redirect( $url, $element_id ) {
global $permalink_manager_external_redirects;
$url = filter_var( $url, FILTER_SANITIZE_URL );
if ( ( empty( $url ) || filter_var( $url, FILTER_VALIDATE_URL ) === false ) && ! empty( $permalink_manager_external_redirects[ $element_id ] ) && isset( $_POST['permalink-manager-external-redirect'] ) ) {
unset( $permalink_manager_external_redirects[ $element_id ] );
} else {
$permalink_manager_external_redirects[ $element_id ] = esc_url( $url );
}
update_option( 'permalink-manager-external-redirects', $permalink_manager_external_redirects );
}
/**
* Add a new tab to the WooCommerce coupon edit page
*
* @param array $tabs The tabs array.
*
* @return array The tabs array with the new tab added.
*/
public static function woocommerce_coupon_tabs( $tabs = array() ) {
$tabs['coupon-url'] = array(
'label' => __( 'Coupon Link', 'permalink-manager' ),
'target' => 'permalink-manager-coupon-url',
'class' => 'permalink-manager-coupon-url',
);
return $tabs;
}
/**
* Add a new panel to the WooCommerce coupon edit page
*/
public static function woocommerce_coupon_panel() {
global $permalink_manager_uris, $post;
$custom_uri = ( ! empty( $permalink_manager_uris[ $post->ID ] ) ) ? $permalink_manager_uris[ $post->ID ] : "";
$html = "<div id=\"permalink-manager-coupon-url\" class=\"panel woocommerce_options_panel custom_uri_container permalink-manager\">";
// URI field
ob_start();
wp_nonce_field( 'permalink-manager-coupon-uri-box', 'permalink-manager-nonce' );
woocommerce_wp_text_input( array(
'id' => 'custom_uri',
'label' => __( 'Coupon URI', 'permalink-manager' ),
'description' => '<span class="duplicated_uri_alert"></span>' . __( 'The URIs are case-insensitive, e.g. <strong>BLACKFRIDAY</strong> and <strong>blackfriday</strong> are equivalent.', 'permalink-manager' ),
'value' => $custom_uri,
'custom_attributes' => array( 'data-element-id' => $post->ID ),
//'desc_tip' => true
) );
$html .= ob_get_contents();
ob_end_clean();
// URI preview
$html .= "<p class=\"form-field coupon-full-url hidden\">";
$html .= sprintf( "<label>%s</label>", __( "Coupon Full URL", "permalink-manager" ) );
$html .= sprintf( "<code>%s/<span>%s</span></code>", trim( get_option( 'home' ), "/" ), $custom_uri );
$html .= "</p>";
$html .= "</div>";
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $html;
}
/**
* Save the custom permalink for specific coupon
*
* @param int $post_id
* @param WC_Coupon $coupon
*/
public static function woocommerce_save_coupon_uri( $post_id, $coupon ) {
// Verify nonce at first
if ( ! isset( $_POST['permalink-manager-nonce'] ) || ! wp_verify_nonce( $_POST['permalink-manager-nonce'], 'permalink-manager-coupon-uri-box' ) ) {
return;
}
// Do not do anything if post is auto-saved
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
$old_uri = Permalink_Manager_URI_Functions::get_single_uri( $post_id, false, true, false );
$new_uri = ( ! empty( $_POST['custom_uri'] ) ) ? $_POST['custom_uri'] : "";
if ( $old_uri !== $new_uri ) {
Permalink_Manager_URI_Functions::save_single_uri( $post_id, $new_uri, false, true );
}
}
/**
* Check the URL contains a coupon code, and if it does, it adds the coupon code to the cart and redirects to the cart page
*
* @param array $query The query array.
*
* @return array The query array.
*/
public static function woocommerce_detect_coupon_code( $query ) {
global $woocommerce, $pm_query;
// Check if custom URI with coupon URL is requested
if ( ! empty( $query['shop_coupon'] ) && ! empty( $pm_query['id'] ) ) {
// Check if cart/shop page is set & redirect to it
$shop_page_id = wc_get_page_id( 'shop' );
$cart_page_id = wc_get_page_id( 'cart' );
if ( ! empty( $cart_page_id ) && WC()->cart->get_cart_contents_count() > 0 ) {
$redirect_page = $cart_page_id;
} else if ( ! empty( $shop_page_id ) ) {
$redirect_page = $shop_page_id;
}
$coupon_code = get_the_title( $pm_query['id'] );
// Set-up session
if ( ! WC()->session->has_session() ) {
WC()->session->set_customer_session_cookie( true );
}
// Add the discount code
if ( ! WC()->cart->has_discount( $coupon_code ) ) {
$woocommerce->cart->add_discount( sanitize_text_field( $coupon_code ) );
}
// Do redirect
if ( ! empty( $redirect_page ) ) {
wp_safe_redirect( get_permalink( $redirect_page ) );
exit();
}
}
return $query;
}
}
@@ -0,0 +1,322 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Additional hooks for "Permalink Manager Pro"
*/
class Permalink_Manager_Pro_License {
public $update_checker;
public function __construct() {
define( 'PERMALINK_MANAGER_PRO', true );
$plugin_name = preg_replace( '/(.*)\/([^\/]+\/[^\/]+.php)$/', '$2', PERMALINK_MANAGER_FILE );
// Permalink Manager Pro Alerts
add_filter( 'permalink_manager_alerts', array( $this, 'pro_alerts' ), 9, 3 );
// Check for updates
add_action( 'plugins_loaded', array( $this, 'check_for_updates' ), 10 );
add_action( 'admin_init', array( $this, 'reload_license_key' ), 10 );
add_action( 'wp_ajax_pm_get_exp_date', array( $this, 'get_expiration_date' ), 9 );
// Display License info on "Plugins" page
add_action( "after_plugin_row_{$plugin_name}", array( $this, 'license_info_bar' ), 10, 2 );
}
/**
* Get the license key from the database, constant defined in wp-config.php file, or $_POST variable
*
* @param string $load_from_db If set to true, the function will load the license key from the database, even if it's defined in wp-config.php.
*
* @return string The license key.
*/
public static function get_license_key( $load_from_db = false ) {
$permalink_manager_options = get_option( 'permalink-manager', array() );
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
// Key defined in wp-config.php
if ( ( defined( 'PMP_LICENCE_KEY' ) || defined( 'PMP_LICENSE_KEY' ) ) && empty( $load_from_db ) ) {
$license_key = defined( 'PMP_LICENCE_KEY' ) ? PMP_LICENCE_KEY : PMP_LICENSE_KEY;
} // Network licence key (multisite)
else if ( is_multisite() ) {
$site_licence_key = get_site_option( 'permalink-manager-licence-key' );
// A. Move the license key to site options
if ( ! empty( $site_licence_key ) && ! is_array( $site_licence_key ) ) {
$new_license_key = $site_licence_key;
} // B. Save the new license key in the plugin settings
else if ( ! empty( $_POST['licence']['licence_key'] ) ) {
$new_license_key = sanitize_text_field( $_POST['licence']['licence_key'] );
}
if ( ! empty( $new_license_key ) ) {
$site_licence_key = array(
'licence_key' => sanitize_text_field( $new_license_key )
);
update_site_option( 'permalink-manager-licence-key', $site_licence_key );
}
$license_key = ( ! empty( $site_licence_key['licence_key'] ) ) ? $site_licence_key['licence_key'] : '';
} // Single website license key
else if ( ! empty( $_POST['licence']['licence_key'] ) ) {
$license_key = sanitize_text_field( $_POST['licence']['licence_key'] );
} else {
$license_key = ( ! empty( $permalink_manager_options['licence']['licence_key'] ) ) ? $permalink_manager_options['licence']['licence_key'] : "";
}
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
return preg_replace( "/[^a-zA-Z0-9-]/", "", $license_key );
}
/**
* Check if the saved license key matches the "developer" format
*
* @param $license_key
*
* @return bool
*/
public static function is_dev_license_key( $license_key ) {
return preg_match( '/^([A-G0-9]{4,8})-([A-G0-9]{4,8})$/', $license_key ) ? true : false;
}
/**
* Load the update checker class and create an instance of it
*/
public function check_for_updates() {
$license_key = self::get_license_key();
// Load Plugin Update Checker by YahnisElsts
require_once PERMALINK_MANAGER_DIR . '/includes/vendor/plugin-update-checker/plugin-update-checker.php';
$this->update_checker = Puc_v4_Factory::buildUpdateChecker( "https://updates.permalinkmanager.pro/?action=get_metadata&slug=permalink-manager-pro&licence_key={$license_key}", PERMALINK_MANAGER_FILE, "permalink-manager-pro" );
add_filter( 'puc_request_info_result-permalink-manager-pro', array( $this, 'update_pro_info' ), 99, 2 );
}
/**
* Check if the license key was changed and if so, delete the cached license data and get the new license data from the server
*/
public function reload_license_key() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
if ( ! empty( $_POST['licence']['licence_key'] ) || ( ! empty( $_REQUEST['action'] ) && $_REQUEST['action'] == 'pm_get_exp_date' ) || ( ! empty( $_REQUEST['puc_slug'] ) && $_REQUEST['puc_slug'] == 'permalink-manager-pro' ) ) {
delete_site_transient( 'permalink_manager_active' );
$this->update_checker->requestInfo();
} // Sync the license data saved in DB after license key was set in wp-config.php file
else if ( defined( 'PMP_LICENCE_KEY' ) || defined( 'PMP_LICENSE_KEY' ) ) {
$db_license_key = self::get_license_key( true );
$license_key = self::get_license_key();
if ( ! empty( $db_license_key ) && ! empty( $license_key ) && $db_license_key !== $license_key ) {
delete_site_transient( 'permalink_manager_active' );
$this->update_checker->requestInfo();
}
}
}
/**
* Check if the cached license key needs to be changed
*
* @param stdClass $raw The raw response from the server.
* @param array $result The response object from the API call.
*
* @return stdClass The plugin info
*/
public function update_pro_info( $raw, $result ) {
$license_key = self::get_license_key();
$permalink_manager_active = ( empty( $_POST['licence']['licence_key'] ) ) ? get_site_transient( 'permalink_manager_active' ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
// A. Do not do anything - the license info was saved before
if ( ! empty( $license_key ) && ( $permalink_manager_active == $license_key ) ) {
return $raw;
} // B. The license info was not removed or not downloaded before
else if ( empty( $permalink_manager_active ) && is_array( $result ) && ! empty( $result['body'] ) && ! empty( $license_key ) ) {
$plugin_info = json_decode( $result['body'] );
if ( is_object( $plugin_info ) && isset( $plugin_info->version ) ) {
$exp_date = ( ! empty( $plugin_info->expiration_date ) && strlen( $plugin_info->expiration_date ) > 6 ) ? strtotime( $plugin_info->expiration_date ) : '-';
$websites = ( ! empty( $plugin_info->websites ) ) ? $plugin_info->websites : '';
$license_info = array(
'licence_key' => $license_key,
'expiration_date' => $exp_date,
'websites' => $websites
);
if ( is_multisite() ) {
update_site_option( 'permalink-manager-licence-key', $license_info );
} else {
Permalink_Manager_Actions::save_settings( 'licence', $license_info, false );
}
set_site_transient( 'permalink_manager_active', $license_key, 12 * HOUR_IN_SECONDS );
}
}
return $raw;
}
/**
* Get license expiration date
*
* @param bool $basic_check
* @param bool $empty_if_valid
* @param bool $update_available
*
* @return int|string
*/
public static function get_expiration_date( $basic_check = false, $empty_if_valid = false, $update_available = true ) {
global $permalink_manager_options;
// Get expiration info & the licence key
if ( is_multisite() ) {
$site_licence_key = get_site_option( 'permalink-manager-licence-key' );
$exp_date = ( ! empty( $site_licence_key['expiration_date'] ) ) ? $site_licence_key['expiration_date'] : false;
$license_key = ( ! empty( $site_licence_key['licence_key'] ) ) ? $site_licence_key['licence_key'] : "";
$websites = ( ! empty( $site_licence_key['websites'] ) ) ? $site_licence_key['websites'] : "";
} else {
$exp_date = ( ! empty( $permalink_manager_options['licence']['expiration_date'] ) ) ? $permalink_manager_options['licence']['expiration_date'] : false;
$license_key = ( ! empty( $permalink_manager_options['licence']['licence_key'] ) ) ? $permalink_manager_options['licence']['licence_key'] : "";
$websites = ( ! empty( $permalink_manager_options['licence']['websites'] ) ) ? $permalink_manager_options['licence']['websites'] : "";
}
$license_info_page = ( ! empty( $license_key ) ) ? self::get_license_info_link( $license_key ) : "#";
// There is no license key defined
if ( empty( $license_key ) ) {
$settings_page_url = Permalink_Manager_Admin_Functions::get_admin_url( "&section=settings" );
// translators: %s is the URL to the settings page where users can manage their license keys.
$expiration_info = sprintf( __( 'Please paste the license key to access all Permalink Manager Pro updates & features <a href="%s" target="_blank">on this page</a>.', 'permalink-manager' ), $settings_page_url );
$expired = 3;
} // License key is invalid
else if ( $exp_date == '-' || ! preg_match( '/^([A-Z0-9]{5,8}-){1}[A-Z0-9]{5,8}((-[A-Z0-9]{5,8}){2})?$/i', $license_key ) ) {
$expiration_info = __( 'Your Permalink Manager Pro license key is invalid!', 'permalink-manager' );
$expired = 3;
} else {
// Key expired
if ( ! empty( $exp_date ) && $exp_date < time() ) {
// translators: %s is the URL to the Permalink Manager page where users can manage their license keys.
$expiration_info = sprintf( __( '<strong>Your Permalink Manager Pro license has expired.</strong><br />Please renew your license key using <a href="%s" target="_blank">this link</a> to restore access to plugin updates.', 'permalink-manager' ), $license_info_page );
$expired = 2;
} // License key is abused
else if ( ! empty( $exp_date ) && ! empty( $websites ) && $update_available === false ) {
$expiration_info = sprintf( __( 'Your Permalink Manager Pro license is already in use on another website and cannot be used to request automatic update for this domain.', 'permalink-manager' ), $license_info_page ) . " ";
// translators: %s is the URL to the Permalink Manager page where users can manage their license keys.
$expiration_info .= sprintf( __( 'For further information, visit the <a href="%s" target="_blank"> License info</a> page.', 'permalink-manager' ), $license_info_page );
$expired = 3;
} // Valid lifetime license key
else if ( gmdate( "Y", intval( $exp_date ) ) > gmdate( 'Y', strtotime( "+10 years" ) ) ) {
$expiration_info = __( 'You own a lifetime license key.', 'permalink-manager' );
$expired = 0;
} // License key is valid
else if ( $exp_date ) {
// License key will expire in less than month & do not display the alert if the developer license key is used
if ( $exp_date - MONTH_IN_SECONDS < time() && ! preg_match( '/^([A-G0-9]{4,8})-([A-G0-9]{4,8})$/', $license_key ) ) {
// translators: %s is the Permalink Manager's license key expiry date.
$expiration_info = sprintf( __( 'Your Permalink Manager Pro license key will expire on <strong>%s</strong>. Please renew it to maintain access to plugin updates and technical support!', 'permalink-manager' ), wp_date( get_option( 'date_format' ), $exp_date ) ) . " ";
// translators: %s is the URL to the Permalink Manager page where users can manage their license keys.
$expiration_info .= sprintf( __( 'For further information, visit the <a href="%s" target="_blank"> License info</a> page.', 'permalink-manager' ), $license_info_page );
$expired = 1;
} // License key can be used
else {
// Translators: %1$s is the expiration date, %2$s is the URL to the license info page.
$expiration_info = sprintf( __( 'Your license key is valid until %1$s.<br />To prolong it please go to <a href="%2$s" target="_blank">this page</a> for more information.', 'permalink-manager' ), gmdate( get_option( 'date_format' ), $exp_date ), $license_info_page );
$expired = 0;
}
} // Expiration data could not be downloaded
else {
$expiration_info = __( 'Expiration date could not be downloaded at this moment. Please try again in a few minutes.', 'permalink-manager' );
$expired = 0;
}
}
// Do not return any text alert
if ( $basic_check || ( $empty_if_valid && $expired == 0 ) ) {
return $expired;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
if ( ! empty( $_REQUEST['action'] ) && $_REQUEST['action'] == 'pm_get_exp_date' ) {
echo wp_kses_post( $expiration_info );
die();
} else {
return $expiration_info;
}
}
/**
* Get the URL to the license info page on the plugin's website
*
* @param $license_key
*
* @return string
*/
static function get_license_info_link( $license_key ) {
$license_key = ( self::is_dev_license_key( $license_key ) ) ? '' : $license_key;
$license_link = ( ! empty( $license_key ) ) ? sprintf( "https://permalinkmanager.pro/license-info/%s/", trim( $license_key ) ) : "https://permalinkmanager.pro/license-info/";
return esc_url( $license_link );
}
/**
* Display license status in the "Plugins" table
*
* @param string $plugin_file
* @param array $plugin_data
*/
function license_info_bar( $plugin_file, $plugin_data ) {
global $wp_list_table;
$column_count = ( ! empty( $wp_list_table ) ) ? $wp_list_table->get_column_count() : 3;
// $update_available = (empty($plugin_data['package']) && !empty($plugin_data['update'])) ? false : true;
$exp_info_text = self::get_expiration_date( false, true, false );
$exp_info_code = self::get_expiration_date( true, true, false );
if ( ! empty( $exp_info_text ) && $exp_info_code >= 1 ) {
printf( '<tr class="plugin-update-tr permalink-manager-pro_license-info active" data-slug="%s" data-plugin="%s"><td colspan="%d" class="plugin-update colspanchange plugin_license_info_row">', esc_attr( $plugin_data['slug'] ), esc_attr( $plugin_file ), esc_attr( $column_count ) );
printf( '<div class="update-message notice inline notice-error notice-alt">%s</div>', wp_kses_post( wpautop( $exp_info_text ) ) );
printf( '</td></tr>' );
}
}
/**
* Display license nag inside the URI Editor
*
*/
public static function license_info_uri_editor() {
$exp_info_text = self::get_expiration_date( false, true, false );
$exp_info_code = self::get_expiration_date( true, true, false );
if ( ! empty( $exp_info_text ) && $exp_info_code == 2 ) {
return sprintf( '<div class="notice inline notice-error notice-alt">%s</div>', wp_kses_post( wpautop( $exp_info_text ) ) );
}
return '';
}
/**
* Hide "Buy Permalink Manager Pro" alert
*
* @param array $alerts
*
* @return array
*/
function pro_alerts( $alerts = array() ) {
// Check expiration date
$exp_info_text = self::get_expiration_date( false, true, false );
$exp_info_code = self::get_expiration_date( true, true, false );
if ( ! empty( $exp_info_text ) && $exp_info_code >= 2 ) {
$alerts['licence_key'] = array( 'txt' => $exp_info_text, 'type' => 'notice-error', 'plugin_only' => true, 'dismissed_time' => DAY_IN_SECONDS * 3 );
}
return $alerts;
}
}
@@ -0,0 +1,906 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* A set of functions for processing and applying the custom permalink to posts
*/
class Permalink_Manager_URI_Functions_Post {
public function __construct() {
add_action( 'admin_init', array( $this, 'admin_init' ), 99, 3 );
add_filter( '_get_page_link', array( $this, 'custom_post_permalinks' ), 99, 2 );
add_filter( 'page_link', array( $this, 'custom_post_permalinks' ), 99, 3 );
add_filter( 'post_link', array( $this, 'custom_post_permalinks' ), 99, 3 );
add_filter( 'post_type_link', array( $this, 'custom_post_permalinks' ), 99, 3 );
add_filter( 'attachment_link', array( $this, 'custom_post_permalinks' ), 99, 2 );
add_filter( 'permalink_manager_uris', array( $this, 'exclude_homepage' ), 99 );
add_filter( 'url_to_postid', array( $this, 'url_to_postid' ), 9 );
add_filter( 'get_sample_permalink_html', array( $this, 'edit_uri_box' ), 20, 5 );
add_action( 'save_post', array( $this, 'update_post_hook' ), 99, 1 );
add_action( 'edit_attachment', array( $this, 'update_post_hook' ), 99, 1 );
add_action( 'wp_insert_post', array( $this, 'insert_post_hook' ), 99, 1 );
add_action( 'add_attachment', array( $this, 'insert_post_hook' ), 99, 1 );
add_action( 'wp_trash_post', array( $this, 'remove_post_hook' ), 100, 1 );
add_action( 'delete_post', array( $this, 'remove_post_hook' ), 100, 1 );
}
/**
* Add "Custom Permalink" input field to "Quick Edit" form
*/
function admin_init() {
$post_types = Permalink_Manager_Helper_Functions::get_post_types_array();
// Add "URI Editor" to "Quick Edit" for all post_types
foreach ( $post_types as $post_type => $label ) {
add_filter( "manage_{$post_type}_posts_columns", array( $this, 'quick_edit_column' ) );
add_filter( "manage_{$post_type}_posts_custom_column", array( $this, 'quick_edit_column_content' ), 10, 2 );
}
}
/**
* Apply the custom permalinks to the posts
*
* @param string $permalink
* @param WP_Post|int $post
*
* @return string
*/
static function custom_post_permalinks( $permalink, $post, $leavename = false ) {
global $permalink_manager_uris, $permalink_manager_options, $permalink_manager_ignore_permalink_filters;
// Do not filter permalinks in Customizer
if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
return $permalink;
}
// Do not filter in WPML String Editor
if ( ! empty( $_REQUEST['icl_ajx_action'] ) && $_REQUEST['icl_ajx_action'] == 'icl_st_save_translation' ) {
return $permalink;
}
// WPML (prevent duplicated posts)
if ( ! empty( $_REQUEST['trid'] ) && ! empty( $_REQUEST['skip_sitepress_actions'] ) ) {
return $permalink;
}
// Do not run when metaboxes are loaded with Gutenberg
/* if ( ! empty( $_REQUEST['meta-box-loader'] ) && empty( $_POST['custom_uri'] ) ) {
return $permalink;
}*/
// Do not filter if $permalink_manager_ignore_permalink_filters global is set
if ( ! empty( $permalink_manager_ignore_permalink_filters ) ) {
return $permalink;
}
$post = ( is_integer( $post ) ) ? get_post( $post ) : $post;
// Do not run if post object is invalid or the permalink is not string
if ( empty( $post ) || empty( $post->ID ) || empty( $post->post_type ) || ! is_string( $permalink ) ) {
return $permalink;
}
// Start with homepage URL
$home_url = Permalink_Manager_Permastructure_Functions::get_permalink_base( $post );
// Check if the post is excluded
if ( ! empty( $post->post_type ) && Permalink_Manager_Helper_Functions::is_post_excluded( $post ) && $post->post_type !== 'attachment' ) {
return $permalink;
}
// 2A. Do not change permalink of frontpage
if ( Permalink_Manager_Helper_Functions::is_front_page( $post->ID ) ) {
return $permalink;
} // 2B. Do not change permalink for drafts and future posts (+ remove trailing slash from them)
else if ( in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft', 'future' ) ) ) {
return $permalink;
}
// 3. Save the old permalink to a separate variable
$old_permalink = $permalink;
// 4. Filter only the posts with custom permalink assigned
if ( isset( $permalink_manager_uris[ $post->ID ] ) ) {
// Encode URI?
if ( ! empty( $permalink_manager_options['general']['decode_uris'] ) ) {
$permalink = "{$home_url}/" . rawurldecode( "/{$permalink_manager_uris[$post->ID]}" );
} else {
$permalink = "{$home_url}/" . Permalink_Manager_Helper_Functions::encode_uri( "{$permalink_manager_uris[$post->ID]}" );
}
// Keep the slug editor in Block Editor wherever it may help
if ( $leavename && is_admin() ) {
$placeholder = Permalink_Manager_Permastructure_Functions::get_post_tag( $post->post_type );
$post_name_esc = preg_quote( get_page_uri( $post->ID ), '/' );
$permalink = preg_replace( "/\/({$post_name_esc})\/?$/", "/{$placeholder}/", $permalink );
}
} else if ( $post->post_type == 'attachment' && $post->post_parent > 0 && $post->post_parent != $post->ID && ! empty( $permalink_manager_uris[ $post->post_parent ] ) ) {
$permalink = "{$home_url}/{$permalink_manager_uris[$post->post_parent]}/attachment/{$post->post_name}";
}
return apply_filters( 'permalink_manager_filter_final_post_permalink', $permalink, $post, $old_permalink );
}
/**
* Check if the provided slug is unique and then update it with SQL query.
*
* @param string $slug
* @param int $id
* @param bool $preview_mode
*
* @return string
*/
static function update_slug_by_id( $slug, $id, $preview_mode = false ) {
global $wpdb;
// Update slug and make it unique
$slug = ( empty( $slug ) ) ? get_the_title( $id ) : $slug;
$slug = sanitize_title( $slug );
$new_slug = wp_unique_post_slug( $slug, $id, get_post_status( $id ), get_post_type( $id ), 0 );
if ( ! $preview_mode ) {
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_name = %s WHERE ID = %d", $new_slug, $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
clean_post_cache( $id );
}
return $new_slug;
}
/**
* Get the currently used custom permalink (or default/empty URI)
*
* @param int $post_id
* @param bool $native_uri
* @param bool $no_fallback
*
* @return string
*/
public static function get_post_uri( $post_id, $native_uri = false, $no_fallback = false ) {
global $permalink_manager_uris;
// Check if input is post object
$post_id = ( isset( $post_id->ID ) ) ? $post_id->ID : $post_id;
if ( ! empty( $permalink_manager_uris[ $post_id ] ) ) {
$final_uri = $permalink_manager_uris[ $post_id ];
} else if ( ! $no_fallback ) {
$final_uri = self::get_default_post_uri( $post_id, $native_uri );
} else {
$final_uri = '';
}
return $final_uri;
}
/**
* Get the default custom permalink (not overwritten by the user) or native permalink (unfiltered)
*
* @param WP_Post|int $post
* @param bool $native_uri
* @param bool $check_if_disabled
*
* @return string
*/
public static function get_default_post_uri( $post, $native_uri = false, $check_if_disabled = false ) {
global $permalink_manager_uris, $permalink_manager_permastructs, $wp_post_types;
// Disable WPML adjust ID filter
if ( class_exists( 'SitePress' ) ) {
add_filter( 'wpml_disable_term_adjust_id', '__return_true', 999 );
}
// Load all bases & post
$post = is_object( $post ) ? $post : get_post( $post );
// Check if post ID is defined (and front page permalinks should be empty)
if ( empty( $post->ID ) || Permalink_Manager_Helper_Functions::is_front_page( $post->ID ) ) {
return '';
}
$post_type = $post->post_type;
if ( empty( $post->post_name ) ) {
$pre_post_name = Permalink_Manager_Helper_Functions::sanitize_title( $post->post_title );
$post_name = wp_unique_post_slug( $pre_post_name, $post->ID, 'publish', $post->post_type, $post->post_parent );
} else {
$post_name = $post->post_name;
}
// 1A. Check if post type is allowed
if ( $check_if_disabled && Permalink_Manager_Helper_Functions::is_post_type_disabled( $post_type ) ) {
return '';
}
// 1A. Get the native permastructure
if ( $post_type == 'attachment' ) {
$parent_page = ( $post->post_parent > 0 && $post->post_parent != $post->ID ) ? get_post( $post->post_parent ) : false;
if ( ! empty( $parent_page->ID ) ) {
$parent_page_uri = ( ! empty( $permalink_manager_uris[ $parent_page->ID ] ) ) ? $permalink_manager_uris[ $parent_page->ID ] : get_page_uri( $parent_page->ID );
} else {
$parent_page_uri = "";
}
$native_permastructure = ( $parent_page ) ? trim( $parent_page_uri, "/" ) . "/attachment" : "";
} else {
$native_permastructure = Permalink_Manager_Permastructure_Functions::get_default_permastruct( $post_type );
}
// 1B. Get the permastructure
if ( $native_uri ) {
$permastructure = $native_permastructure;
} else {
$permastructure = ( ! empty( $permalink_manager_permastructs['post_types'][ $post_type ] ) ) ? $permalink_manager_permastructs['post_types'][ $post_type ] : $native_permastructure;
$permastructure = apply_filters( 'permalink_manager_filter_permastructure', $permastructure, $post );
}
// 1C. Set the permastructure
$default_base = ( ! empty( $permastructure ) ) ? trim( $permastructure, '/' ) : "";
// 2A. Get the date
$date = explode( " ", date( 'Y m d H i s', strtotime( $post->post_date ) ) );
$monthname = sanitize_title( date_i18n( 'F', strtotime( $post->post_date ) ) );
// 2B. Get the author (if needed)
$author = '';
if ( strpos( $default_base, '%author%' ) !== false ) {
$authordata = get_userdata( $post->post_author );
$author = $authordata->user_nicename;
}
// 2C. Get the post type slug
if ( ! empty( $wp_post_types[ $post_type ] ) ) {
if ( ! empty( $wp_post_types[ $post_type ]->rewrite['slug'] ) ) {
$post_type_slug = $wp_post_types[ $post_type ]->rewrite['slug'];
} else if ( is_string( $wp_post_types[ $post_type ]->rewrite ) ) {
$post_type_slug = $wp_post_types[ $post_type ]->rewrite;
}
}
$post_type_slug = ( ! empty( $post_type_slug ) ) ? $post_type_slug : $post_type;
$post_type_slug = apply_filters( 'permalink_manager_filter_post_type_slug', $post_type_slug, $post, $post_type );
$post_type_slug = preg_replace( '/(%([^%]+)%\/?)/', '', $post_type_slug );
// 3B. Get the full slug
$post_name = Permalink_Manager_Helper_Functions::remove_slashes( $post_name );
$custom_slug = $full_custom_slug = Permalink_Manager_Helper_Functions::force_custom_slugs( $post_name, $post, true, null, false );
$post_title_slug = Permalink_Manager_Helper_Functions::force_custom_slugs( $post_name, $post, true, 1 );
$full_native_slug = $post_name;
// 3A. Fix for hierarchical CPT (start)
if ( $post->ancestors && is_post_type_hierarchical( $post_type ) ) {
$full_native_slug = Permalink_Manager_Helper_Functions::get_post_full_slug( $post, false, true );
$full_custom_slug = Permalink_Manager_Helper_Functions::get_post_full_slug( $post, false, false );
}
// 3B. Allow filter the default slug (only custom permalinks)
if ( ! $native_uri ) {
$full_slug = apply_filters( 'permalink_manager_filter_default_post_slug', $full_custom_slug, $post, $post_name );
} else {
$full_slug = $full_native_slug;
}
$post_type_tag = Permalink_Manager_Permastructure_Functions::get_post_tag( $post_type );
// 3C. Get the standard tags and replace them with their values
$tags = array( '%year%', '%monthnum%', '%monthname%', '%day%', '%hour%', '%minute%', '%second%', '%post_id%', '%author%', '%post_type%' );
$tags_replacements = array( $date[0], $date[1], $monthname, $date[2], $date[3], $date[4], $date[5], $post->ID, $author, $post_type_slug );
$default_uri = str_replace( $tags, $tags_replacements, $default_base );
// 3D. Get the slug tags
$slug_tags = array( $post_type_tag, "%postname%", "%postname_flat%", "%pagename_flat%", "%{$post_type}_flat%", "%native_slug%", '%native_title%' );
$slug_tags_replacement = array( $full_slug, $full_slug, $custom_slug, $custom_slug, $custom_slug, $full_native_slug, $post_title_slug );
$do_not_append_slug = Permalink_Manager_Permastructure_Functions::is_slug_tag_present( $default_uri, $slug_tags, $post );
// 3E. Replace the post tags with slugs or append the slug if no post tag is defined
if ( ! empty( $do_not_append_slug ) ) {
$default_uri = str_replace( $slug_tags, $slug_tags_replacement, $default_uri );
} else {
$default_uri .= "/{$full_slug}";
}
// 4. Replace taxonomies
$taxonomies = get_taxonomies();
if ( $taxonomies ) {
foreach ( $taxonomies as $taxonomy ) {
// 0. Check if taxonomy tag is present
if ( strpos( $default_uri, "%{$taxonomy}" ) === false ) {
continue;
}
// 1. Get terms assigned to this post
$terms = wp_get_object_terms( $post->ID, $taxonomy );
// 2. Sort the terms
if ( ! empty( $terms ) ) {
$terms = wp_list_sort( $terms, array(
'parent' => 'DESC',
'term_id' => 'ASC',
) );
}
// 3A. Try to use Yoast SEO Primary Term
$replacement_term = Permalink_Manager_Helper_Functions::get_primary_term( $post->ID, $taxonomy, false );
// 3B. Get the first assigned term to this taxonomy
if ( empty( $replacement_term ) ) {
$replacement_term = ( ! is_wp_error( $terms ) && ! empty( $terms ) && is_object( $terms[0] ) ) ? Permalink_Manager_Helper_Functions::get_lowest_element( $terms[0], $terms ) : '';
}
$replacement_term = apply_filters( 'permalink_manager_filter_post_terms', $replacement_term, $post, $terms, $taxonomy, $native_uri );
// 4A. Custom URI as term base
if ( ! empty( $replacement_term->term_id ) && strpos( $default_uri, "%{$taxonomy}_custom_uri%" ) !== false && ! empty( $permalink_manager_uris["tax-{$replacement_term->term_id}"] ) ) {
$mode = 1;
} // 4B. Hierarchical term base
else if ( ! empty( $replacement_term->term_id ) && strpos( $default_uri, "%{$taxonomy}_flat%" ) === false && strpos( $default_uri, "%{$taxonomy}_top%" ) === false && is_taxonomy_hierarchical( $taxonomy ) ) {
$mode = 2;
} // 4C. Force flat/non-hierarchical term base - get the highest level term (if %taxonomy_top% tag is used)
else if ( strpos( $default_uri, "%{$taxonomy}_top%" ) !== false ) {
$mode = 3;
} // 4D. Force flat/non-hierarchical term base - get the lowest level term (if %taxonomy_flat% tag is used)
else {
$mode = 4;
}
// Get the replacement slug (custom + native)
$replacement = Permalink_Manager_Helper_Functions::get_term_full_slug( $replacement_term, $terms, $mode, $native_uri );
$native_replacement = Permalink_Manager_Helper_Functions::get_term_full_slug( $replacement_term, $terms, $mode, true );
// Trim slashes
$replacement = trim( $replacement, '/' );
$native_replacement = trim( $native_replacement, '/' );
// Filter final category slug
$replacement = apply_filters( 'permalink_manager_filter_term_slug', $replacement, $replacement_term, $post, $terms, $taxonomy, $native_uri );
// 4. Do the replacement
$default_uri = ( ! empty( $replacement ) ) ? str_replace( array( "%{$taxonomy}%", "%{$taxonomy}_flat%", "%{$taxonomy}_custom_uri%", "%{$taxonomy}_top%" ), $replacement, $default_uri ) : $default_uri;
$default_uri = ( ! empty( $native_replacement ) ) ? str_replace( "%{$taxonomy}_native_slug%", $native_replacement, $default_uri ) : $default_uri;
}
}
// Restore WPML adjust ID filter
if ( class_exists( 'SitePress' ) ) {
remove_filter( 'wpml_disable_term_adjust_id', '__return_true', 999 );
}
return apply_filters( 'permalink_manager_filter_default_post_uri', $default_uri, $post->post_name, $post, $post_name, $native_uri );
}
/**
* Exclude the page selected as "Front page"
*
* @param array $uris
*
* @return array
*/
function exclude_homepage( $uris ) {
// Find the homepage URI
$homepage_id = get_option( 'page_on_front' );
if ( is_array( $uris ) && ! empty( $uris[ $homepage_id ] ) ) {
unset( $uris[ $homepage_id ] );
}
return $uris;
}
/**
* Support url_to_postid() function
*
* @param string $url
*
* @return string
*/
public function url_to_postid( $url ) {
global $pm_query;
// Filter only defined URLs
if ( empty( $url ) ) {
return $url;
}
// Make sure that $pm_query global is not changed
$old_pm_query = $pm_query;
$post = Permalink_Manager_Core_Functions::detect_post( array(), $url, true );
$pm_query = $old_pm_query;
if ( ! empty( $post->ID ) ) {
$native_url = "/?p={$post->ID}";
}
return ( ! empty( $native_url ) ) ? $native_url : $url;
}
/**
* Get array with all post items based on the user-selected settings in the "Bulk tools" form
*
* @return array|false
*/
public static function get_items() {
global $wpdb, $permalink_manager_options;
// Check if post types & statuses are not empty
if ( empty( $_POST['post_types'] ) || empty( $_POST['post_statuses'] ) ) {
return false;
}
$post_types_array = array_map( 'sanitize_key', $_POST['post_types'] );
$post_statuses_array = array_map( 'sanitize_key', $_POST['post_statuses'] );
$post_types_in = Permalink_Manager_Helper_Functions::prepare_array_for_sql_in( $post_types_array, false );
$post_statuses_in = Permalink_Manager_Helper_Functions::prepare_array_for_sql_in( $post_statuses_array, false );
// Filter the posts by IDs
$where = '';
if ( ! empty( $_POST['ids'] ) ) {
// Remove whitespaces and prepare array with IDs and/or ranges
$ids = esc_sql( preg_replace( '/\s*/m', '', $_POST['ids'] ) );
preg_match_all( "/([\d]+(?:-?[\d]+)?)/x", $ids, $groups );
// Prepare the extra ID filters
$where .= "AND (";
foreach ( $groups[0] as $group ) {
$where .= ( $group == reset( $groups[0] ) ) ? "" : " OR ";
// A. Single number
if ( is_numeric( $group ) ) {
$where .= "(ID = {$group})";
} // B. Range
else if ( substr_count( $group, '-' ) ) {
$range_edges = explode( "-", $group );
$where .= "(ID BETWEEN {$range_edges[0]} AND {$range_edges[1]})";
}
}
$where .= ")";
}
// Get excluded items
$excluded_posts = (array) apply_filters( 'permalink_manager_excluded_post_ids', array() );
if ( ! empty( $excluded_posts ) ) {
$where .= sprintf( " AND ID NOT IN (%s) ", Permalink_Manager_Helper_Functions::prepare_array_for_sql_in( $excluded_posts ) );
}
// Support for attachments
$attachment_support = ( in_array( 'attachment', $post_types_array ) ) ? " OR (post_type = 'attachment')" : "";
// Check the auto-update mode
// A. Allow only user-approved posts
if ( ! empty( $permalink_manager_options["general"]["auto_update_uris"] ) && $permalink_manager_options["general"]["auto_update_uris"] == 2 ) {
$where .= " AND pm.meta_value IN (1, -1) ";
} // B. Allow all posts not disabled by the user
else {
$where .= " AND (pm.meta_value IS NULL OR pm.meta_value IN (1, -1)) ";
}
// Get the rows before they are altered
$query = "SELECT post_type, post_title, post_name, ID FROM {$wpdb->posts} AS p LEFT JOIN {$wpdb->postmeta} AS pm ON pm.post_ID = p.ID AND pm.meta_key = 'auto_update_uri' WHERE ((post_status IN ({$post_statuses_in}) AND post_type IN ({$post_types_in})){$attachment_support}) {$where}";
$query = apply_filters( 'permalink_manager_get_items_query', $query, $where, 'post_types' );
return $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
}
/**
* Process the permalinks/slugs "Find & replace" & "Regenerate/rest" tool
*
* @param array $chunk
* @param string $mode
* @param string $operation
* @param string $old_string
* @param string $new_string
* @param bool $preview_mode
*
* @return array|false
*/
public static function bulk_process_items( $chunk = null, $mode = '', $operation = '', $old_string = '', $new_string = '', $preview_mode = false ) {
// Reset variables
$updated_slugs_count = 0;
$updated_array = array();
// Get the rows before they are altered
$posts_to_update = ( ! empty( $chunk ) ) ? $chunk : self::get_items();
if ( empty( $operation ) ) {
return false;
}
// Now if the array is not empty use IDs from each subarray as a key
if ( $posts_to_update ) {
foreach ( $posts_to_update as $row ) {
// Get default & native URL
$native_uri = self::get_default_post_uri( $row['ID'], true );
$default_uri = self::get_default_post_uri( $row['ID'] );
$old_custom_uri = Permalink_Manager_URI_Functions::get_single_uri( $row['ID'], true, true );
$old_uri = ( ! empty( $old_custom_uri ) ) ? $old_custom_uri : $native_uri;
$old_post_name = $row['post_name'];
if ( $operation == 'regenerate' ) {
if ( $mode == 'slugs' ) {
$new_uri = $old_uri;
$new_post_name = Permalink_Manager_Helper_Functions::sanitize_title( $row['post_title'] );
} else if ( $mode == 'native' ) {
$new_uri = $native_uri;
$new_post_name = $old_post_name;
} else {
$new_uri = $default_uri;
$new_post_name = $old_post_name;
}
} else {
list( $new_post_name, $new_uri ) = Permalink_Manager_Helper_Functions::replace_uri_slug( $old_string, $new_string, $old_post_name, $old_uri, $mode );
}
// Check if native slug should be changed
if ( $mode == 'slugs' && $old_post_name !== $new_post_name ) {
$new_post_name = self::update_slug_by_id( $new_post_name, $row['ID'], $preview_mode );
}
$new_uri = apply_filters( 'permalink_manager_pre_update_post_uri', $new_uri, $row['ID'], $old_uri, $native_uri, $default_uri );
if ( ! ( empty( $new_uri ) ) && ( $old_custom_uri !== $new_uri ) || ( $old_post_name !== $new_post_name ) ) {
if ( ! $preview_mode && ( $old_custom_uri !== $new_uri ) ) {
Permalink_Manager_URI_Functions::save_single_uri( $row['ID'], $new_uri, false, false );
do_action( 'permalink_manager_updated_post_uri', $row['ID'], $new_uri, $old_uri, $native_uri, $default_uri );
}
$updated_array[] = array( 'item_title' => $row['post_title'], 'ID' => $row['ID'], 'old_uri' => $old_uri, 'new_uri' => $new_uri, 'old_slug' => $old_post_name, 'new_slug' => $new_post_name );
$updated_slugs_count ++;
}
}
// Save all custom permalinks
if ( ! $preview_mode ) {
Permalink_Manager_URI_Functions::save_all_uris();
}
$output = array( 'updated' => $updated_array, 'updated_count' => $updated_slugs_count );
wp_reset_postdata();
}
return ( ! empty( $output ) ) ? $output : false;
}
/**
* Save the custom permalinks in "Bulk URI Editor" tool
*
* @return array|false
*/
static public function update_all_permalinks() {
// Setup needed variables
$updated_slugs_count = 0;
$updated_array = array();
$new_uris = isset( $_POST['uri'] ) ? $_POST['uri'] : array();
// Double check if the slugs and ids are stored in arrays
if ( ! is_array( $new_uris ) ) {
$new_uris = explode( ',', $new_uris );
}
if ( ! empty( $new_uris ) ) {
foreach ( $new_uris as $id => $new_uri ) {
// Prepare variables
$this_post = get_post( $id );
$old_uri = Permalink_Manager_URI_Functions::get_single_uri( $this_post, false, true );
$final_uri = self::save_uri( $this_post, $new_uri, false, false, false );
if ( $final_uri ) {
$updated_array[] = array( 'item_title' => get_the_title( $id ), 'ID' => $id, 'old_uri' => $old_uri, 'new_uri' => $final_uri );
$updated_slugs_count ++;
}
}
// Save all custom permalinks
Permalink_Manager_URI_Functions::save_all_uris();
$output = array( 'updated' => $updated_array, 'updated_count' => $updated_slugs_count );
}
return ( ! empty( $output ) ) ? $output : false;
}
/**
* Allow to edit URIs from "Edit Post" admin pages
*
* @param string $html
* @param int $id
* @param string $new_title
* @param string $new_slug
* @param WP_Post $post
*
* @return string
*/
function edit_uri_box( $html, $id, $new_title, $new_slug, $post ) {
// Detect auto drafts
$autosave = ( ! empty( $new_title ) && empty( $new_slug ) ) ? true : false;
// Check if the post is excluded
if ( empty( $post->post_type ) || Permalink_Manager_Helper_Functions::is_post_excluded( $post, true ) ) {
$show_uri_editor = false;
} else {
$show_uri_editor = true;
}
// Stop the hook (if needed)
$show_uri_editor = apply_filters( "permalink_manager_show_uri_editor_post", $show_uri_editor, $post, $post->post_type );
if ( ! $show_uri_editor ) {
return $html;
}
// Make sure that home URL ends with slash
$home_url = Permalink_Manager_Permastructure_Functions::get_permalink_base( $post );
// A. Display the original permalink on the front-page editor
if ( Permalink_Manager_Helper_Functions::is_front_page( $id ) ) {
preg_match( '/href="([^"]+)"/mi', $html, $matches );
$sample_permalink = ( ! empty( $matches[1] ) ) ? $matches[1] : "";
} else {
// B. Do not change anything if post is not saved yet (display sample permalink instead)
if ( $autosave || empty( $post->post_status ) ) {
$sample_permalink_uri = self::get_default_post_uri( $id );
} // C. Display custom URI (if set) or default custom URI (for drafts)
else {
$is_draft = ( $post->post_status == 'draft' ) ? true : false;
$sample_permalink_uri = Permalink_Manager_URI_Functions::get_single_uri( $post, ! $is_draft, false );
}
if ( empty( $sample_permalink_uri ) && $post->post_type !== 'attachment' ) {
return $html;
}
// Decode URI & allow to filter it
$sample_permalink_uri = apply_filters( 'permalink_manager_filter_post_sample_uri', rawurldecode( $sample_permalink_uri ), $post );
// Prepare the sample & default permalink
$sample_permalink = sprintf( "%s/<span class=\"editable\">%s</span>", $home_url, str_replace( "//", "/", $sample_permalink_uri ) );
}
$sample_permalink_html = sprintf( "<span id=\"sample-permalink\"><span class=\"sample-permalink-span\"><a id=\"sample-permalink\" href=\"%s\">%s</a></span></span>&nbsp;", strip_tags( $sample_permalink ), $sample_permalink );
// 1. Overwrite the sample permalink
if ( preg_match( '/(<span id="sample-permalink"><a.*<\/a><\/span>)/m', $html ) ) {
$new_html = preg_replace( '/(<span id="sample-permalink"><a.*<\/a><\/span>)/m', $sample_permalink_html, $html );
} else if ( preg_match( '/(<a id="sample-permalink"[^<]*>.*<\/a>)/m', $html ) ) {
$new_html = preg_replace( '/(<a id="sample-permalink"[^<]*>.*<\/a>)/m', $sample_permalink_html, $html );
} else {
$new_html = $html;
}
// 2. Append the Permalink Editor
$new_html .= Permalink_Manager_UI_Elements::display_uri_box( $post );
// 3. Hide the "Edit" slug button
$new_html = str_replace( 'edit-slug button', 'edit-slug button hidden', $new_html );
// 4. Append hidden field with native slug
$new_html .= ( ! empty( $post->post_name ) && strpos( $new_html, 'editable-post-name-full' ) === false ) ? "<span id=\"editable-post-name-full\">{$post->post_name}</span>" : "";
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return $new_html;
}
/**
* Add "Current URI" input field to "Quick Edit" form
*
* @param array $columns
*
* @return array mixed
*/
function quick_edit_column( $columns ) {
global $current_screen;
// Get post type
$post_type = ( ! empty( $current_screen->post_type ) ) ? $current_screen->post_type : false;
// Check if post type is disabled
if ( $post_type && Permalink_Manager_Helper_Functions::is_post_type_disabled( $post_type ) ) {
return $columns;
}
return ( is_array( $columns ) ) ? array_merge( $columns, array( 'permalink-manager-col' => __( 'Custom permalink', 'permalink-manager' ) ) ) : $columns;
}
/**
* Display the URI of the current post in the "Custom Permalink" column
*
* @param string $column_name The name of the column to display. In this case, we named our column permalink-manager-col.
* @param int $post_id The ID of the term.
*/
function quick_edit_column_content( $column_name, $post_id ) {
global $permalink_manager_uris, $permalink_manager_options;
if ( $column_name == 'permalink-manager-col' ) {
$exclude_drafts = ( isset( $permalink_manager_options['general']['ignore_drafts'] ) ) ? $permalink_manager_options['general']['ignore_drafts'] : false;
$is_draft = ( get_post_status( $post_id ) == 'draft' ) ? true : false;
// A. Disable the 'Quick Edit' form for draft posts if 'Exclude drafts' option is turned on
if ( $exclude_drafts && $is_draft ) {
$disabled = 1;
} // B. Get auto-update settings
else {
$auto_update_val = get_post_meta( $post_id, 'auto_update_uri', true );
$disabled = ( ! empty( $auto_update_val ) ) ? $auto_update_val : $permalink_manager_options['general']['auto_update_uris'];
}
$uri = ( ! empty( $permalink_manager_uris[ $post_id ] ) ) ? rawurldecode( $permalink_manager_uris[ $post_id ] ) : self::get_post_uri( $post_id, true, $is_draft );
printf( '<span class="permalink-manager-col-uri" data-disabled="%s">%s</span>', esc_attr( intval( $disabled ) ), esc_html( $uri ) );
}
}
/**
* Save the custom permalink
*
* @param int|WP_Post $post
* @param string $new_uri
* @param bool $is_new_post
* @param int|bool $auto_update_mode
*
* @return bool|string
*/
static function save_uri( $post, $new_uri = '', $is_new_post = false, $auto_update_mode = false, $db_save = true ) {
global $permalink_manager_options;
// Do not do anything if post is auto-saved
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return false;
}
// Get the post object
if ( is_numeric( $post ) ) {
$post_object = get_post( $post );
} else if ( is_a( $post, 'WP_Post' ) ) {
$post_object = $post;
} else {
return false;
}
// Check if post is allowed
if ( empty( $post_object->post_type ) || ( $is_new_post && empty( $post_object->post_title ) ) || Permalink_Manager_Helper_Functions::is_post_excluded( $post_object, true, $is_new_post ) ) {
return false;
}
// Manage 'Auto-update URI' settings
if ( ! empty( $auto_update_mode ) ) {
$auto_update_uri = $auto_update_mode;
update_post_meta( $post_object->ID, 'auto_update_uri', $auto_update_mode );
} elseif ( $auto_update_mode === 0 ) {
$auto_update_uri = $permalink_manager_options['general']['auto_update_uris'];
delete_post_meta( $post_object->ID, 'auto_update_uri' );
} else {
$auto_update_uri = get_post_meta( $post_object->ID, 'auto_update_uri', true );
$auto_update_uri = ( ! empty( $auto_update_uri ) ) ? $auto_update_uri : $permalink_manager_options['general']['auto_update_uris'];
}
$default_uri = self::get_default_post_uri( $post_object );
$native_uri = self::get_default_post_uri( $post_object, true );
$old_uri = self::get_post_uri( $post_object->ID, false, true );
if ( $is_new_post ) {
$new_uri = $default_uri;
$allow_save = apply_filters( 'permalink_manager_allow_new_post_uri', true, $post_object );
} else {
$new_uri = ( ( $new_uri == '' || $auto_update_uri == 1 ) && ! in_array( $post_object->post_status, array( 'draft', 'auto-draft' ) ) ) ? $default_uri : Permalink_Manager_Helper_Functions::sanitize_title( $new_uri, true );
$allow_save = apply_filters( 'permalink_manager_allow_update_post_uri', true, $post_object );
}
$new_uri = apply_filters( 'permalink_manager_pre_update_post_uri', $new_uri, $post_object->ID, $old_uri, $native_uri, $default_uri );
// The update URI process is stopped by the hook above or disabled in "Auto-update" settings
if ( ! $allow_save || ( ! empty( $auto_update_uri ) && $auto_update_uri == 2 ) ) {
return false;
}
// Save the URI only if $new_uri variable is set
if ( ! empty( $new_uri ) && $new_uri !== $old_uri ) {
Permalink_Manager_URI_Functions::save_single_uri( $post_object->ID, $new_uri, false, $db_save );
$uri_saved = true;
} // The $new_uri variable is empty or no change is detected
else {
$uri_saved = false;
}
do_action( 'permalink_manager_updated_post_uri', $post_object->ID, $new_uri, $old_uri, $native_uri, $default_uri, $uri_saved );
return ( $uri_saved ) ? $new_uri : false;
}
/**
* Generate the custom permalink when 'wp_insert_post' action is triggered
*
* @param int $post_id Post ID.
*/
function insert_post_hook( $post_id ) {
global $permalink_manager_uris;
// Do not trigger if post is a revision or imported via WP All Import (URI should be set after the post meta is added)
if ( empty( $post_id ) || wp_is_post_revision( $post_id ) || ( ! empty( $_REQUEST['page'] ) && $_REQUEST['page'] == 'pmxi-admin-import' ) ) {
return;
}
// Stop when products are imported with WooCommerce importer
if ( ! empty( $_REQUEST['action'] ) && $_REQUEST['action'] == 'woocommerce_do_ajax_product_import' ) {
return;
}
// Do not process REST API requests originating from Gutenberg JS functions
if ( defined( 'REST_REQUEST' ) && REST_REQUEST && ! empty( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
return;
}
// Do not do anything if the custom permalink was generated before or 'custom_uri' field is present in the request
if ( isset( $permalink_manager_uris[ $post_id ] ) || ( isset( $_POST['custom_uri'] ) ) ) {
return;
}
self::save_uri( $post_id, '', true, 0 );
}
/**
* Save the custom permalink when 'save_post' action is triggered
*
* @param int $post_id Term ID.
*/
static function update_post_hook( $post_id ) {
// Verify nonce at first
if ( ! isset( $_POST['permalink-manager-nonce'] ) || ! wp_verify_nonce( $_POST['permalink-manager-nonce'], 'permalink-manager-edit-uri-box' ) ) {
return;
}
// Do not do anything if the field with URI or element ID are not present or different from the provided post ID
if ( ! isset( $_POST['custom_uri'] ) || empty( $_POST['permalink-manager-edit-uri-element-id'] ) || $_POST['permalink-manager-edit-uri-element-id'] != $post_id ) {
return;
}
// Check if the user can edit this post
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Fix for revisions
$post_id = wp_is_post_revision( $post_id ) ? wp_is_post_revision( $post_id ) : $post_id;
$post = get_post( $post_id );
// Get auto-update URI setting (if empty use global setting)
if ( isset( $_POST["auto_update_uri"] ) && is_numeric( $_POST["auto_update_uri"] ) ) {
$auto_update_mode = intval( $_POST["auto_update_uri"] );
} else {
$auto_update_mode = false;
}
// Update the slug (if changed)
if ( isset( $_POST['permalink-manager-edit-uri-element-slug'] ) && isset( $_POST['native_slug'] ) && ( $_POST['native_slug'] !== $post->post_name ) ) {
// Make sure that '_wp_old_slug' is saved
if ( ! empty( $_POST['post_name'] ) || ( isset( $_POST['action'] ) && in_array( $_POST['action'], array( 'pm_save_permalink', 'editpost' ) ) ) ) {
$post_after = clone $post;
$post_after->post_name = sanitize_title( $_POST['native_slug'] );
wp_check_for_changed_slugs( $post_id, $post_after, $post );
}
self::update_slug_by_id( $_POST['native_slug'], $post_id );
}
self::save_uri( $post_id, $_POST['custom_uri'], false, $auto_update_mode );
}
/**
* Remove URI from options array after post is moved to the trash
*
* @param int $post_id
*/
function remove_post_hook( $post_id ) {
Permalink_Manager_URI_Functions::remove_single_uri( $post_id, false, true );
}
}
@@ -0,0 +1,682 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* A set of functions for processing and applying the custom permalink to terms.
*/
class Permalink_Manager_URI_Functions_Tax {
public function __construct() {
add_action( 'init', array( $this, 'init' ), 100 );
add_action( 'rest_api_init', array( $this, 'init' ) );
add_filter( 'term_link', array( $this, 'custom_tax_permalinks' ), 999, 2 );
}
/**
* Allow to edit URIs from "Edit Term" admin pages (register hooks)
*/
public function init() {
global $permalink_manager_options;
$all_taxonomies = Permalink_Manager_Helper_Functions::get_taxonomies_array();
// Add "URI Editor" to "Quick Edit" for all taxonomies
foreach ( $all_taxonomies as $tax => $label ) {
// Check if taxonomy is allowed
if ( Permalink_Manager_Helper_Functions::is_taxonomy_disabled( $tax ) ) {
continue;
}
add_action( "edited_{$tax}", array( $this, 'update_term_uri' ), 10, 2 );
add_action( "create_{$tax}", array( $this, 'update_term_uri' ), 10, 2 );
add_action( "delete_{$tax}", array( $this, 'remove_term_uri' ), 10, 2 );
// Check the user capabilities
if ( is_admin() ) {
$edit_uris_cap = ( ! empty( $permalink_manager_options['general']['edit_uris_cap'] ) ) ? $permalink_manager_options['general']['edit_uris_cap'] : 'publish_posts';
if ( current_user_can( $edit_uris_cap ) ) {
add_action( "{$tax}_add_form_fields", array( $this, 'edit_uri_box' ), 10, 1 );
add_action( "{$tax}_edit_form_fields", array( $this, 'edit_uri_box' ), 10, 1 );
add_filter( "manage_edit-{$tax}_columns", array( $this, 'quick_edit_column' ) );
add_filter( "manage_{$tax}_custom_column", array( $this, 'quick_edit_column_content' ), 20, 3 );
}
}
}
}
/**
* Apply the custom permalinks to the terms
*
* @param string $permalink
* @param WP_Term|int $term
*
* @return string
*/
function custom_tax_permalinks( $permalink, $term ) {
global $permalink_manager_uris, $permalink_manager_options, $permalink_manager_ignore_permalink_filters;
// Do not filter permalinks in Customizer
if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
return $permalink;
}
// Do not filter in WPML String Editor
if ( ! empty( $_REQUEST['icl_ajx_action'] ) && $_REQUEST['icl_ajx_action'] == 'icl_st_save_translation' ) {
return $permalink;
}
// Do not filter if $permalink_manager_ignore_permalink_filters global is set
if ( ! empty( $permalink_manager_ignore_permalink_filters ) ) {
return $permalink;
}
$term = ( is_numeric( $term ) ) ? get_term( $term ) : $term;
// Check if the term is allowed
if ( empty( $term->term_id ) || Permalink_Manager_Helper_Functions::is_term_excluded( $term ) || ! is_string( $permalink ) ) {
return $permalink;
}
// Get term id
$term_id = $term->term_id;
// Save the old permalink to separate variable
$old_permalink = $permalink;
if ( isset( $permalink_manager_uris["tax-{$term_id}"] ) ) {
// Start with homepage URL
$permalink = Permalink_Manager_Permastructure_Functions::get_permalink_base( $term );
// Encode URI?
if ( ! empty( $permalink_manager_options['general']['decode_uris'] ) ) {
$permalink .= rawurldecode( "/{$permalink_manager_uris["tax-{$term_id}"]}" );
} else {
$permalink .= Permalink_Manager_Helper_Functions::encode_uri( "/{$permalink_manager_uris["tax-{$term_id}"]}" );
}
} else if ( ! empty( $permalink_manager_options['general']['decode_uris'] ) ) {
$permalink = rawurldecode( $permalink );
}
return apply_filters( 'permalink_manager_filter_final_term_permalink', $permalink, $term, $old_permalink );
}
/**
* Check if the provided slug is unique and then update it with SQL query.
*
* @param string $slug
* @param int $id
* @param bool $preview_mode
*
* @return string
*/
static function update_slug_by_id( $slug, $id, $preview_mode = false ) {
global $wpdb;
// Update slug and make it unique
$term = get_term( intval( $id ) );
$slug = ( empty( $slug ) ) ? get_the_title( $term->name ) : $slug;
$slug = sanitize_title( $slug );
$new_slug = wp_unique_term_slug( $slug, $term );
if ( ! $preview_mode ) {
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->terms} SET slug = %s WHERE term_id = %d", $new_slug, $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
}
return $new_slug;
}
/**
* Get the currently used custom permalink (or default/empty URI)
*
* @param int $term_id
* @param bool $native_uri
* @param bool $no_fallback
*
* @return string
*/
public static function get_term_uri( $term_id, $native_uri = false, $no_fallback = false ) {
global $permalink_manager_uris;
// Check if input is term object
$term = ( isset( $term_id->term_id ) ) ? $term_id->term_id : get_term( $term_id );
if ( ! empty( $permalink_manager_uris["tax-{$term_id}"] ) ) {
$final_uri = $permalink_manager_uris["tax-{$term_id}"];
} else if ( ! $no_fallback ) {
$final_uri = self::get_default_term_uri( $term->term_id, $native_uri );
} else {
$final_uri = '';
}
return $final_uri;
}
/**
* Get the default custom permalink (not overwritten by the user) or native permalink (unfiltered)
*
* @param WP_Term|int $term
* @param bool $native_uri
* @param bool $check_if_disabled
*
* @return string
*/
public static function get_default_term_uri( $term, $native_uri = false, $check_if_disabled = false ) {
global $permalink_manager_permastructs, $wp_taxonomies;
// Disable WPML adjust ID filter
if ( class_exists( 'SitePress' ) ) {
add_filter( 'wpml_disable_term_adjust_id', '__return_true', 999 );
}
// 1. Load all bases & term
$term = is_object( $term ) ? $term : get_term( $term );
// $term_id = $term->term_id;
$taxonomy_name = $term->taxonomy;
$taxonomy = get_taxonomy( $taxonomy_name );
$term_slug = $term->slug;
$top_parent_slug = '';
// 1A. Check if taxonomy is allowed
if ( $check_if_disabled && Permalink_Manager_Helper_Functions::is_taxonomy_disabled( $taxonomy ) ) {
return '';
}
// 2A. Get the native permastructure
$native_permastructure = Permalink_Manager_Permastructure_Functions::get_default_permastruct( $taxonomy_name );
// 2B. Get the permastructure
if ( $native_uri || empty( $permalink_manager_permastructs['taxonomies'][ $taxonomy_name ] ) ) {
$permastructure = $native_permastructure;
} else {
$permastructure = apply_filters( 'permalink_manager_filter_permastructure', $permalink_manager_permastructs['taxonomies'][ $taxonomy_name ], $term );
}
// 2C. Set the permastructure
$default_base = ( ! empty( $permastructure ) ) ? trim( $permastructure, '/' ) : "";
// 3A. Check if the taxonomy has custom permastructure set
if ( empty( $default_base ) && ! isset( $permalink_manager_permastructs['taxonomies'][ $taxonomy_name ] ) ) {
if ( 'category' == $taxonomy_name ) {
$default_uri = "?cat={$term->term_id}";
} elseif ( $taxonomy->query_var ) {
$default_uri = "?{$taxonomy->query_var}={$term_slug}";
} else if ( ! empty( $term_slug ) ) {
$default_uri = "?taxonomy={$taxonomy_name}&term={$term_slug}";
} else {
$default_uri = '';
}
} // 3B. Use custom permastructure
else {
$default_uri = $default_base;
// 3B. Get the full slug
$term_slug = Permalink_Manager_Helper_Functions::remove_slashes( $term_slug );
$custom_slug = $full_custom_slug = Permalink_Manager_Helper_Functions::force_custom_slugs( $term_slug, $term, true, null, false );
$term_title_slug = Permalink_Manager_Helper_Functions::force_custom_slugs( $term_slug, $term, true, 1 );
$full_native_slug = $term_slug;
// Add ancestors to hierarchical taxonomy
if ( is_taxonomy_hierarchical( $taxonomy_name ) ) {
$ancestors = get_ancestors( $term->term_id, $taxonomy_name, 'taxonomy' );
foreach ( $ancestors as $ancestor ) {
$ancestor_term = get_term( $ancestor, $taxonomy_name );
$full_native_slug = $ancestor_term->slug . '/' . $full_native_slug;
$full_custom_slug = Permalink_Manager_Helper_Functions::force_custom_slugs( $ancestor_term->slug, $ancestor_term ) . '/' . $full_custom_slug;
}
// Get top parent term
if ( strpos( $default_uri, "%{$taxonomy_name}_top%" ) === false || strpos( $default_uri, "%term_top%" ) === false ) {
$top_parent_slug = Permalink_Manager_Helper_Functions::get_term_full_slug( $term, $ancestors, 3, $native_uri );
}
}
// Allow filter the default slug (only custom permalinks)
if ( ! $native_uri ) {
$full_slug = apply_filters( 'permalink_manager_filter_default_term_slug', $full_custom_slug, $term, $term->name );
} else {
$full_slug = $full_native_slug;
}
// Get the taxonomy slug
if ( ! empty( $wp_taxonomies[ $taxonomy_name ]->rewrite['slug'] ) ) {
$taxonomy_name_slug = $wp_taxonomies[ $taxonomy_name ]->rewrite['slug'];
} else if ( is_string( $wp_taxonomies[ $taxonomy_name ]->rewrite ) ) {
$taxonomy_name_slug = $wp_taxonomies[ $taxonomy_name ]->rewrite;
} else {
$taxonomy_name_slug = $taxonomy_name;
}
$taxonomy_name_slug = apply_filters( 'permalink_manager_filter_taxonomy_slug', $taxonomy_name_slug, $term, $taxonomy_name );
$slug_tags = array( "%term_name%", "%term_flat%", "%{$taxonomy_name}%", "%{$taxonomy_name}_flat%", "%term_top%", "%{$taxonomy_name}_top%", "%native_slug%", "%native_title%", "%taxonomy%", "%term_id%" );
$slug_tags_replacement = array( $full_slug, $custom_slug, $full_slug, $custom_slug, $top_parent_slug, $top_parent_slug, $full_native_slug, $term_title_slug, $taxonomy_name_slug, $term->term_id );
// Check if any term tag is present in custom permastructure
$do_not_append_slug = Permalink_Manager_Permastructure_Functions::is_slug_tag_present( $default_uri, $slug_tags, $term );
// Replace the term tags with slugs or append the slug if no term tag is defined
if ( ! empty( $do_not_append_slug ) ) {
$default_uri = str_replace( $slug_tags, $slug_tags_replacement, $default_uri );
} else {
$default_uri .= "/{$full_slug}";
}
}
// Restore WPML adjust ID filter
if ( class_exists( 'SitePress' ) ) {
remove_filter( 'wpml_disable_term_adjust_id', '__return_true', 999 );
}
return apply_filters( 'permalink_manager_filter_default_term_uri', $default_uri, $term->slug, $term, $term_slug, $native_uri );
}
/**
* Get array with all term items based on the user-selected settings in the "Bulk tools" form
*
* @return array|false
*/
public static function get_items() {
global $wpdb, $permalink_manager_options;
// Check if taxonomies are not empty
if ( empty( $_POST['taxonomies'] ) || ! is_array( $_POST['taxonomies'] ) ) {
return false;
}
$taxonomy_names_array = array_map( 'sanitize_key', $_POST['taxonomies'] );
$taxonomy_names_in = Permalink_Manager_Helper_Functions::prepare_array_for_sql_in( $taxonomy_names_array, false );
// Filter the terms by IDs
$where = '';
if ( ! empty( $_POST['ids'] ) ) {
// Remove whitespaces and prepare array with IDs and/or ranges
$ids = esc_sql( preg_replace( '/\s*/m', '', $_POST['ids'] ) );
preg_match_all( "/([\d]+(?:-?[\d]+)?)/x", $ids, $groups );
// Prepare the extra ID filters
$where .= "AND (";
foreach ( $groups[0] as $group ) {
$where .= ( $group == reset( $groups[0] ) ) ? "" : " OR ";
// A. Single number
if ( is_numeric( $group ) ) {
$where .= "(t.term_id = {$group})";
} // B. Range
else if ( substr_count( $group, '-' ) ) {
$range_edges = explode( "-", $group );
$where .= "(t.term_id BETWEEN {$range_edges[0]} AND {$range_edges[1]})";
}
}
$where .= ")";
}
// Get excluded items
$excluded_terms = (array) apply_filters( 'permalink_manager_excluded_term_ids', array() );
if ( ! empty( $excluded_terms ) ) {
$where .= sprintf( " AND t.term_id NOT IN (%s) ", Permalink_Manager_Helper_Functions::prepare_array_for_sql_in( $excluded_terms ) );
}
// Check the auto-update mode
// A. Allow only user-approved posts
if ( ! empty( $permalink_manager_options["general"]["auto_update_uris"] ) && $permalink_manager_options["general"]["auto_update_uris"] == 2 ) {
$where .= " AND tm.meta_value IN (1, -1) ";
} // B. Allow all posts not disabled by the user
else {
$where .= " AND (tm.meta_value IS NULL OR tm.meta_value IN (1, -1)) ";
}
// Get the rows before they are altered
$query = "SELECT t.slug, t.name, t.term_id, tt.taxonomy FROM {$wpdb->terms} AS t INNER JOIN {$wpdb->term_taxonomy} AS tt ON tt.term_id = t.term_id LEFT JOIN {$wpdb->termmeta} AS tm ON (tm.term_id = t.term_id AND tm.meta_key = 'auto_update_uri') WHERE tt.taxonomy IN ({$taxonomy_names_in}) {$where}";
$query = apply_filters( 'permalink_manager_get_items_query', $query, $where, 'taxonomies' );
return $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
}
/**
* Process the permalinks/slugs "Find & replace" & "Regenerate/rest" tool
*
* @param array $chunk
* @param string $mode
* @param string $operation
* @param string $old_string
* @param string $new_string
* @param bool $preview_mode
*
* @return array|false
*/
public static function bulk_process_items( $chunk = null, $mode = '', $operation = '', $old_string = '', $new_string = '', $preview_mode = false ) {
// Reset variables
$updated_slugs_count = 0;
$updated_array = array();
// Get the rows before they are altered
$terms_to_update = ( ! empty( $chunk ) ) ? $chunk : self::get_items();
if ( empty( $operation ) ) {
return false;
}
// Now if the array is not empty use IDs from each subarray as a key
if ( $terms_to_update ) {
foreach ( $terms_to_update as $row ) {
$this_term = get_term( $row['term_id'] );
// Get default & native URL
$native_uri = self::get_default_term_uri( $this_term, true );
$default_uri = self::get_default_term_uri( $this_term );
$old_custom_uri = Permalink_Manager_URI_Functions::get_single_uri( $row['term_id'], true, true, true );
$old_uri = ( ! empty( $old_custom_uri ) ) ? $old_custom_uri : $native_uri;
$old_term_name = $row['slug'];
if ( $operation == 'regenerate' ) {
if ( $mode == 'slugs' ) {
$new_uri = $old_uri;
$new_term_name = Permalink_Manager_Helper_Functions::sanitize_title( $row['name'] );
} else if ( $mode == 'native' ) {
$new_uri = $native_uri;
$new_term_name = $old_term_name;
} else {
$new_uri = $default_uri;
$new_term_name = $old_term_name;
}
} else {
list( $new_term_name, $new_uri ) = Permalink_Manager_Helper_Functions::replace_uri_slug( $old_string, $new_string, $old_term_name, $old_uri, $mode );
}
// Check if native slug should be changed
if ( $mode == 'slugs' && $old_term_name !== $new_term_name ) {
$new_term_name = self::update_slug_by_id( $new_term_name, $row['term_id'], $preview_mode );
}
$new_uri = apply_filters( 'permalink_manager_pre_update_term_uri', $new_uri, $row['term_id'], $old_uri, $native_uri, $default_uri );
if ( ! ( empty( $new_uri ) ) && ( $old_custom_uri !== $new_uri ) || ( $old_term_name !== $new_term_name ) ) {
if ( ! $preview_mode && ( $old_custom_uri !== $new_uri ) ) {
Permalink_Manager_URI_Functions::save_single_uri( $row['term_id'], $new_uri, true, false );
do_action( 'permalink_manager_updated_term_uri', $row['term_id'], $new_uri, $old_uri, $native_uri, $default_uri );
}
$updated_array[] = array( 'item_title' => $row['name'], 'ID' => $row['term_id'], 'old_uri' => $old_uri, 'new_uri' => $new_uri, 'old_slug' => $old_term_name, 'new_slug' => $new_term_name, 'tax' => $this_term->taxonomy );
$updated_slugs_count ++;
}
}
// Save all custom permalinks
if ( ! $preview_mode ) {
Permalink_Manager_URI_Functions::save_all_uris();
}
$output = array( 'updated' => $updated_array, 'updated_count' => $updated_slugs_count );
wp_reset_postdata();
}
return ( ! empty( $output ) ) ? $output : false;
}
/**
* Save the custom permalinks in "Bulk URI Editor" tool
*
* @return array|false
*/
static public function update_all_permalinks() {
// Setup needed variables
$updated_slugs_count = 0;
$updated_array = array();
$new_uris = isset( $_POST['uri'] ) ? $_POST['uri'] : array();
// Double check if the slugs and ids are stored in arrays
if ( ! is_array( $new_uris ) ) {
$new_uris = explode( ',', $new_uris );
}
if ( ! empty( $new_uris ) ) {
foreach ( $new_uris as $id => $new_uri ) {
// Remove prefix from field name to obtain term id
$term_id = filter_var( str_replace( 'tax-', '', $id ), FILTER_SANITIZE_NUMBER_INT );
$this_term = get_term( $term_id );
$old_uri = Permalink_Manager_URI_Functions::get_single_uri( $term_id, false, true, true );
$new_uri = ( empty( $new_uri ) ) ? null : $new_uri; // If the string is empty, convert it to null to force the default permalink
$final_uri = self::save_uri( $this_term, $new_uri, false, false, false );
if ( $final_uri ) {
$updated_array[] = array( 'item_title' => $this_term->name, 'ID' => $term_id, 'old_uri' => $old_uri, 'new_uri' => $final_uri, 'tax' => $this_term->taxonomy );
$updated_slugs_count ++;
}
}
// Save all custom permalinks
Permalink_Manager_URI_Functions::save_all_uris();
$output = array( 'updated' => $updated_array, 'updated_count' => $updated_slugs_count );
}
return ( ! empty( $output ) ) ? $output : false;
}
/**
* Allow to edit URIs from "New Term" & "Edit Term" admin pages
*
* @param WP_Term $term
*/
public function edit_uri_box( $term = '' ) {
// Check if the term is excluded
if ( empty( $term ) || Permalink_Manager_Helper_Functions::is_term_excluded( $term ) ) {
return;
}
// Stop the hook (if needed)
if ( ! empty( $term->taxonomy ) ) {
$show_uri_editor = apply_filters( "permalink_manager_show_uri_editor_term", true, $term, $term->taxonomy );
if ( ! $show_uri_editor ) {
return;
}
}
$label = __( "Custom URI", "permalink-manager" );
$description = __( "Clear/leave the field empty to use the default permalink.", "permalink-manager" );
// A. New term
if ( empty( $term->term_id ) ) {
$html = "<div class=\"form-field\">";
$html .= sprintf( "<label for=\"term_meta[uri]\">%s</label>", $label );
$html .= "<input type=\"text\" name=\"custom_uri\" id=\"custom_uri\" value=\"\">";
$html .= sprintf( "<p class=\"description\">%s</p>", $description );
$html .= "</div>";
// Append nonce field
$html .= wp_nonce_field( 'permalink-manager-edit-uri-box', 'permalink-manager-nonce', true, false );
} // B. Edit term
else {
$html = "<tr id=\"permalink-manager\" class=\"form-field permalink-manager-edit-term permalink-manager\">";
$html .= sprintf( "<th scope=\"row\"><label for=\"custom_uri\">%s</label></th>", $label );
$html .= "<td><div>";
$html .= Permalink_Manager_UI_Elements::display_uri_box( $term );
$html .= "</div></td>";
$html .= "</tr>";
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $html;
}
/**
* Add "Custom permalink" input field to "Quick Edit" form
*
* @param array $columns
*
* @return array mixed
*/
function quick_edit_column( $columns ) {
return ( is_array( $columns ) ) ? array_merge( $columns, array( 'permalink-manager-col' => __( 'Custom permalink', 'permalink-manager' ) ) ) : $columns;
}
/**
* Display the URI of the current term in the "Custom permalink" column
*
* @param string $content The column content.
* @param string $column_name The name of the column to display. In this case, we named our column permalink-manager-col.
* @param int $term_id The ID of the term.
*
* @return string
*/
function quick_edit_column_content( $content, $column_name, $term_id ) {
global $permalink_manager_uris, $permalink_manager_options;
if ( $column_name == "permalink-manager-col" ) {
$auto_update_val = get_term_meta( $term_id, "auto_update_uri", true );
$disabled = ( ! empty( $auto_update_val ) ) ? $auto_update_val : $permalink_manager_options["general"]["auto_update_uris"];
$uri = ( ! empty( $permalink_manager_uris["tax-{$term_id}"] ) ) ? self::get_term_uri( $term_id ) : self::get_term_uri( $term_id, true );
$content = sprintf( '<span class="permalink-manager-col-uri" data-disabled="%s">%s</span>', intval( $disabled ), $uri );
}
return $content;
}
/**
* Update URI from "Edit Term" admin page / Set the custom permalink for new term item
*
* @param int $term_id Term ID.
* @param int $tt_term_id Term taxonomy ID.
*/
function update_term_uri( $term_id, $tt_term_id ) {
// Term ID must be defined
if ( empty( $term_id ) ) {
return;
}
$current_filter = current_filter();
// Validate the nonce (if the permalink editor is displayed)
if ( isset( $_POST['custom_uri'] ) && ( ! isset( $_POST['permalink-manager-nonce'] ) || ! wp_verify_nonce( $_POST['permalink-manager-nonce'], 'permalink-manager-edit-uri-box' ) ) ) {
return;
} // If term was added via "Edit Post" page, use the default URI
else if ( ! empty( $current_filter ) && strpos( $current_filter, 'wp_ajax_add' ) !== false && empty( $_POST['custom_uri'] ) ) {
$is_new_term = true;
} else if ( ! empty( $current_filter ) && strpos( $current_filter, 'create_' ) !== false ) {
$is_new_term = true;
} else {
$is_new_term = false;
}
// Get auto-update URI setting
if ( isset( $_POST["auto_update_uri"] ) ) {
$auto_update_mode = intval( $_POST["auto_update_uri"] );
} else {
$auto_update_mode = false;
}
// Check if the URI is provided in the input field
if ( isset( $_POST['custom_uri'] ) && empty( $is_new_term ) && empty( $_POST['post_ID'] ) ) {
$new_uri = ( ! empty( $_POST['custom_uri'] ) ) ? Permalink_Manager_Helper_Functions::sanitize_title( $_POST['custom_uri'] ) : '';
} else if ( ! empty( $is_new_term ) ) {
$new_uri = '';
} else {
return;
}
self::save_uri( $term_id, $new_uri, $is_new_term, $auto_update_mode );
}
/**
* Save the custom permalink
*
* @param int|WP_Term $term
* @param string $new_uri
* @param bool $is_new_term
* @param int|bool $auto_update_mode
*
* @return bool
*/
static function save_uri( $term, $new_uri = '', $is_new_term = false, $auto_update_mode = false, $db_save = true ) {
global $permalink_manager_options;
// Get the term object
if ( is_numeric( $term ) ) {
$term_object = get_term( $term );
} else if ( is_a( $term, 'WP_Term' ) ) {
$term_object = $term;
} else {
return false;
}
$term_id = $term_object->term_id;
// Check if the term is allowed
if ( empty( $term_object->taxonomy ) || Permalink_Manager_Helper_Functions::is_term_excluded( $term_object ) || empty( $term_id ) ) {
return false;
}
// Manage 'Auto-update URI' settings
if ( ! empty( $auto_update_mode ) ) {
$auto_update_uri = $auto_update_mode;
update_term_meta( $term_id, "auto_update_uri", $auto_update_mode );
} else if ( $auto_update_mode === 0 ) {
$auto_update_uri = $permalink_manager_options['general']['auto_update_uris'];
delete_term_meta( $term_id, "auto_update_uri" );
} else {
$auto_update_uri = get_term_meta( $term_id, "auto_update_uri", true );
$auto_update_uri = ( ! empty( $auto_update_uri ) ) ? $auto_update_uri : $permalink_manager_options['general']['auto_update_uris'];
}
// Get default & native & user-submitted URIs
$native_uri = self::get_default_term_uri( $term_object, true );
$default_uri = self::get_default_term_uri( $term_object );
$old_uri = self::get_term_uri( $term_id, false, true );
if ( ! empty( $new_uri ) && $auto_update_uri != 1 ) {
$new_uri = Permalink_Manager_Helper_Functions::sanitize_title( $new_uri, true );
} else if ( $is_new_term || $auto_update_uri == 1 || empty( $new_uri ) ) {
$new_uri = $default_uri;
} else {
$new_uri = '';
}
if ( $is_new_term ) {
$allow_save = apply_filters( 'permalink_manager_allow_new_term_uri', true, $term_object );
} else {
$allow_save = apply_filters( 'permalink_manager_allow_update_term_uri', true, $term_object );
}
$new_uri = apply_filters( 'permalink_manager_pre_update_term_uri', $new_uri, $term_id, $old_uri, $native_uri, $default_uri );
// The update URI process is stopped by the hook above or disabled in "Auto-update" settings
if ( ! $allow_save || ( ! empty( $auto_update_uri ) && $auto_update_uri == 2 ) ) {
return false;
}
// Save the URI only if $new_uri variable is set
if ( ! empty( $new_uri ) && $new_uri !== $old_uri ) {
Permalink_Manager_URI_Functions::save_single_uri( $term_id, $new_uri, true, $db_save );
$uri_saved = true;
} // The $new_uri variable is empty or no change is detected
else {
$uri_saved = false;
}
do_action( 'permalink_manager_updated_term_uri', $term_id, $new_uri, $old_uri, $native_uri, $default_uri, $uri_saved );
return ( $uri_saved ) ? $new_uri : false;
}
/**
* Remove URI from options array after term is moved to the trash
*
* @param int $term_id
*/
function remove_term_uri( $term_id ) {
Permalink_Manager_URI_Functions::remove_single_uri( $term_id, true, true );
}
}
@@ -0,0 +1,236 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Functions used to create, edit and remove custom permalinks
*/
class Permalink_Manager_URI_Functions {
public function __construct() {
}
/**
* Get the custom permalink's array key for specific post or term
*
* @param WP_Post|WP_Term|int|string $element
* @param bool $is_tax
*
* @return array
*/
public static function get_single_uri_key( $element, $is_tax = false ) {
if ( ! empty( $element->term_id ) ) {
$is_term = true;
$element_id = $element->term_id;
} else if ( ! empty( $element->ID ) ) {
$is_term = false;
$element_id = $element->ID;
} else if ( is_string( $element ) || is_numeric( $element ) ) {
$is_term = ( strpos( $element, 'tax-' ) !== false ) ? true : $is_tax;
$element_id = preg_replace( '/[^0-9]/', '', $element );
} else {
$element_id = "";
$is_term = null;
}
$array_index = ( $is_term && ! empty( $element_id ) ) ? sprintf( 'tax-%s', $element_id ) : $element_id;
return array( $element_id, $is_term, $array_index );
}
/**
* Get the single custom permalink
*
* @param WP_Post|WP_Term|int $element
* @param bool $native_uri
* @param bool $no_fallback
*/
public static function get_single_uri( $element, $native_uri = false, $no_fallback = false, $is_tax = false ) {
// Get the element key
list( $element_id, $is_term, $array_index ) = self::get_single_uri_key( $element, $is_tax );
if ( $is_term ) {
$final_uri = ( class_exists( 'Permalink_Manager_URI_Functions_Tax' ) ) ? Permalink_Manager_URI_Functions_Tax::get_term_uri( $element_id, $native_uri, $no_fallback ) : '';
} else {
$final_uri = Permalink_Manager_URI_Functions_Post::get_post_uri( $element_id, $native_uri, $no_fallback );
}
return $final_uri;
}
/**
* Save single custom permalink to the custom permalinks array
*
* @param int|string $element
* @param string $element_uri
* @param bool $is_tax
* @param bool $db_save
*/
public static function save_single_uri( $element, $element_uri = null, $is_tax = false, $db_save = false ) {
global $permalink_manager_uris;
// Get the element key
list( $element_id, $is_term, $array_index ) = self::get_single_uri_key( $element, $is_tax );
// Save the custom permalink if the URI is not empty
if ( ! empty( $array_index ) && ! empty( $element_uri ) ) {
$permalink_manager_uris[ $array_index ] = Permalink_Manager_Helper_Functions::sanitize_title( $element_uri, true );
if ( $db_save ) {
self::save_all_uris( $permalink_manager_uris );
}
}
}
/**
* Remove single custom permalink from the custom permalinks array
*
* @param int|string $element
* @param bool $is_tax
* @param bool $db_save
*/
public static function remove_single_uri( $element, $is_tax = false, $db_save = false ) {
global $permalink_manager_uris;
// Get the element key
list( $element_id, $is_term, $array_index ) = self::get_single_uri_key( $element, $is_tax );
// Check if the custom permalink is assigned to this element
if ( ! empty( $array_index ) && isset( $permalink_manager_uris[ $array_index ] ) ) {
unset( $permalink_manager_uris[ $array_index ] );
}
if ( $db_save ) {
self::save_all_uris( $permalink_manager_uris );
}
}
/**
* Find the ID(s) of the element(s) by its custom permalink
*
* @param string $search_query
* @param bool $strict_search
* @param string $content_type
*
* @return bool|string|array
*/
public static function find_uri( $search_query, $strict_search = true, $content_type = null ) {
$custom_permalinks = self::get_all_uris();
$found = false;
if ( $strict_search ) {
$all_uris = array_flip( $custom_permalinks );
$found = ( ! empty( $all_uris[ $search_query ] ) ) ? $all_uris[ $search_query ] : false;
} else {
$found = array();
$search_query = preg_quote( $search_query, '/' );
foreach ( $custom_permalinks as $id => $uri ) {
if ( preg_match( "/\b$search_query\b/i", $uri ) ) {
if ( $content_type == 'taxonomies' && ( strpos( $id, 'tax-' ) !== false ) ) {
$found[] = (int) abs( filter_var( $id, FILTER_SANITIZE_NUMBER_INT ) );
} else if ( $content_type == 'posts' && is_numeric( $id ) ) {
$found[] = (int) filter_var( $id, FILTER_SANITIZE_NUMBER_INT );
} else if ( empty( $content_type ) ) {
$found[] = $id;
}
}
}
}
return $found;
}
/**
* Check if a single URI is duplicated
*
* @param string $uri
* @param int $element_id
* @param array $duplicated_ids
*
* @return bool
*/
public static function is_uri_duplicated( $uri, $element_id, $duplicated_ids = array() ) {
$custom_permalinks = Permalink_Manager_URI_Functions::get_all_uris();
if ( empty( $uri ) || empty( $element_id ) || empty( $custom_permalinks ) ) {
return false;
}
$uri = trim( trim( sanitize_text_field( $uri ) ), "/" );
$element_id = sanitize_text_field( $element_id );
// Keep the URIs in a separate array just here
if ( ! empty( $duplicated_ids ) ) {
$all_duplicates = $duplicated_ids;
} else if ( in_array( $uri, $custom_permalinks ) ) {
$all_duplicates = array_keys( $custom_permalinks, $uri );
}
if ( ! empty( $all_duplicates ) ) {
// Get the language code of current element
$this_uri_lang = apply_filters( 'permalink_manager_get_language_code', '', $element_id );
foreach ( $all_duplicates as $key => $duplicated_id ) {
// Ignore custom redirects
if ( strpos( $key, 'redirect-' ) !== false ) {
unset( $all_duplicates[ $key ] );
continue;
}
if ( $this_uri_lang ) {
$duplicated_uri_lang = apply_filters( 'permalink_manager_get_language_code', '', $duplicated_id );
}
// Ignore the URI for requested element and other elements in other languages to prevent the false alert
if ( ( ! empty( $duplicated_uri_lang ) && $duplicated_uri_lang !== $this_uri_lang ) || $element_id == $duplicated_id ) {
unset( $all_duplicates[ $key ] );
}
}
return ( count( $all_duplicates ) > 0 ) ? true : false;
} else {
return false;
}
}
/**
* Get the array (or statistics) with custom permalinks
*
* @param bool $stats
*
* @return array
*/
public static function get_all_uris( $stats = false ) {
global $permalink_manager_uris;
if ( $stats ) {
$custom_permalinks_size = strlen( serialize( $permalink_manager_uris ) );
$custom_permalinks_count = count( $permalink_manager_uris );
return array( $custom_permalinks_size, $custom_permalinks_count );
} else {
return array_filter( $permalink_manager_uris );
}
}
/**
* Save the array with custom permalinks
*
* @param array $updated_uris
*/
public static function save_all_uris( $updated_uris = null ) {
if ( is_null( $updated_uris ) ) {
global $permalink_manager_uris;
$updated_uris = $permalink_manager_uris;
}
if ( is_array( $updated_uris ) && ! empty( $updated_uris ) ) {
update_option( 'permalink-manager-uris', $updated_uris, false );
}
}
}