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,345 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* SEO plugins integration
*/
class Permalink_Manager_SEO_Plugins {
public function __construct() {
add_action( 'init', array( $this, 'init_hooks' ), 99 );
}
/**
* Add support for SEO plugins using their hooks
*/
function init_hooks() {
// Yoast SEO
add_filter( 'wpseo_xml_sitemap_post_url', array( $this, 'yoast_fix_sitemap_urls' ), 9 );
if ( defined( 'WPSEO_VERSION' ) && version_compare( WPSEO_VERSION, '14.0', '>=' ) ) {
add_action( 'permalink_manager_updated_post_uri', array( $this, 'yoast_update_indexable_permalink' ), 10, 3 );
add_action( 'permalink_manager_updated_term_uri', array( $this, 'yoast_update_indexable_permalink' ), 10, 3 );
add_filter( 'wpseo_canonical', array( $this, 'yoast_fix_canonical' ), 10 );
add_filter( 'wpseo_opengraph_url', array( $this, 'yoast_fix_canonical' ), 10 );
add_filter( 'wpseo_dynamic_permalinks_enabled', '__return_true', 5 );
}
// Breadcrumbs
add_filter( 'wpseo_breadcrumb_links', array( $this, 'filter_breadcrumbs' ), 9 );
add_filter( 'rank_math/frontend/breadcrumb/items', array( $this, 'filter_breadcrumbs' ), 9 );
add_filter( 'seopress_pro_breadcrumbs_crumbs', array( $this, 'filter_breadcrumbs' ), 9 );
add_filter( 'woocommerce_get_breadcrumb', array( $this, 'filter_breadcrumbs' ), 9 );
add_filter( 'slim_seo_breadcrumbs_links', array( $this, 'filter_breadcrumbs' ), 9 );
add_filter( 'aioseo_breadcrumbs_trail', array( $this, 'filter_breadcrumbs' ), 9 );
add_filter( 'avia_breadcrumbs_trail', array( $this, 'filter_breadcrumbs' ), 100 );
}
/**
* Get the HTTP protocol of the home URL and use it in Yoast SEO sitemap permalinks
*
* @param string $permalink The permalink in the sitemap
*
* @return string The sitemap's permalink
*/
function yoast_fix_sitemap_urls( $permalink ) {
if ( class_exists( 'WPSEO_Utils' ) ) {
$home_url = WPSEO_Utils::home_url();
$home_protocol = parse_url( $home_url, PHP_URL_SCHEME );
$permalink = preg_replace( "/^http(s)?/", $home_protocol, $permalink );
}
return $permalink;
}
/**
* Update the permalink in the Yoast SEO indexable table when the permalink is changed
*
* @param int $element_id The ID of the post/term element that was updated.
* @param string $new_uri The new URI of the element.
* @param string $old_uri The old URI of the element.
*/
function yoast_update_indexable_permalink( $element_id, $new_uri, $old_uri ) {
global $wpdb;
if ( ! empty( $new_uri ) && ! empty( $old_uri ) && $new_uri !== $old_uri ) {
if ( current_filter() == 'permalink_manager_updated_term_uri' ) {
$permalink = get_term_link( (int) $element_id );
$object_type = 'term';
} else {
$permalink = get_permalink( $element_id );
$object_type = 'post';
}
if ( ! empty( $permalink ) ) {
$permalink_hash = strlen( $permalink ) . ':' . md5( $permalink );
$wpdb->update( "{$wpdb->prefix}yoast_indexable", array( 'permalink' => $permalink, 'permalink_hash' => $permalink_hash ), array( 'object_id' => $element_id, 'object_type' => $object_type ), array( '%s', '%s' ), array( '%d', '%s' ) );
}
}
}
/**
* Filter the canonical permalink used by SEO using 'wpseo_canonical' & 'wpseo_opengraph_url' hooks
*
* @param string $url The canonical URL that Yoast SEO has generated.
*
* @return string the URL.
*/
function yoast_fix_canonical( $url ) {
global $pm_query, $wp_rewrite;
if ( ! empty( $pm_query['id'] ) ) {
$element = get_queried_object();
if ( ! empty( $element->ID ) && ! empty( $element->post_type ) ) {
$new_url = get_permalink( $element->ID );
// Do not filter if custom canonical URL is set
$yoast_canonical_url = get_post_meta( $element->ID, '_yoast_wpseo_canonical', true );
if ( ! empty( $yoast_canonical_url ) ) {
return $url;
}
if ( is_home() ) {
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$new_url = ( $paged > 1 ) ? sprintf( '%s/%s/%d', trim( $new_url, '/' ), $wp_rewrite->pagination_base, $paged ) : $new_url;
} else {
$paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;
$new_url = ( $paged > 1 ) ? sprintf( '%s/%d', trim( $new_url, '/' ), $paged ) : $new_url;
}
} else if ( ! empty( $element->taxonomy ) && ! empty( $element->term_id ) ) {
$new_url = get_term_link( $element, $element->taxonomy );
// Do not filter if custom canonical URL is set
if ( class_exists( 'WPSEO_Taxonomy_Meta' ) ) {
$yoast_canonical_url = WPSEO_Taxonomy_Meta::get_term_meta( $element, $element->taxonomy, 'canonical' );
if ( ! empty( $yoast_canonical_url ) ) {
return $url;
}
}
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
if ( $paged > 1 ) {
$new_url = sprintf( '%s/%s/%d', trim( $new_url, '/' ), $wp_rewrite->pagination_base, $paged );
}
}
$url = ( ! empty( $new_url ) ) ? $new_url : $url;
$url = Permalink_Manager_Core_Functions::control_trailing_slashes( $url );
}
return $url;
}
/**
* Filter the breadcrumbs array to match the structure of currently requested URL
*
* @param array $links The current breadcrumb links.
*
* @return array The $links array.
*/
function filter_breadcrumbs( $links ) {
// Get post type permastructure settings
global $permalink_manager_options, $post, $wpdb, $wp, $wp_current_filter;
// Check if the filter should be activated
if ( empty( $permalink_manager_options['general']['yoast_breadcrumbs'] ) ) {
return $links;
}
// Get current post/page/term (if available)
$queried_element = get_queried_object();
if ( ! empty( $queried_element->ID ) ) {
$element_id = $queried_element->ID;
} else if ( ! empty( $queried_element->term_id ) ) {
$element_id = "tax-{$queried_element->term_id}";
} else if ( defined( 'REST_REQUEST' ) && ! empty( $post->ID ) ) {
$element_id = $post->ID;
}
// Get the custom permalink (if available) or the current request URL (if unavailable)
$custom_uri = ( ! empty( $element_id ) ) ? Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null ) : '';
if ( ! empty( $custom_uri ) ) {
$custom_uri = preg_replace( "/([^\/]+)$/", '', $custom_uri );
} else {
return $links;
}
$custom_uri_parts = explode( '/', trim( $custom_uri ) );
$breadcrumbs = array();
$snowball = '';
$available_taxonomies = Permalink_Manager_Helper_Functions::get_taxonomies_array( null, null, true );
$available_post_types = Permalink_Manager_Helper_Functions::get_post_types_array( null, null, true );
$available_post_types_archive = Permalink_Manager_Helper_Functions::get_post_types_array( 'archive_slug', null, true );
$current_filter = end( $wp_current_filter );
// Get Yoast Meta (the breadcrumbs titles can be changed in Yoast metabox)
$yoast_meta_terms = get_option( 'wpseo_taxonomy_meta' );
// Check what array keys should be used for breadcrumbs ("All In One SEO" uses a more complicated schema)
if ( $current_filter == 'aioseo_breadcrumbs_trail' ) {
$breadcrumb_key_text = 'label';
$breadcrumb_key_url = 'link';
$is_aioseo = true;
} else if ( in_array( $current_filter, array( 'wpseo_breadcrumb_links', 'slim_seo_breadcrumbs_links' ) ) ) {
$breadcrumb_key_text = 'text';
$breadcrumb_key_url = 'url';
$is_aioseo = false;
} else {
$breadcrumb_key_text = 0;
$breadcrumb_key_url = 1;
$is_aioseo = false;
}
// Get internal breadcrumb elements
foreach ( $custom_uri_parts as $slug ) {
if ( empty( $slug ) ) {
continue;
}
$snowball = ( empty( $snowball ) ) ? $slug : "{$snowball}/{$slug}";
// 1A. Try to match any custom URI
$uri = trim( $snowball, "/" );
$element = Permalink_Manager_URI_Functions::find_uri( $uri, true );
if ( ! empty( $element ) && strpos( $element, 'tax-' ) !== false ) {
$element_id = intval( preg_replace( "/[^0-9]/", "", $element ) );
$element = get_term( $element_id );
} else if ( is_numeric( $element ) ) {
$element = get_post( $element );
}
// 1B. Try to get term
if ( empty( $element ) && ! empty( $available_taxonomies ) ) {
$sql = sprintf( "SELECT t.term_id, t.name, tt.taxonomy FROM {$wpdb->terms} AS t LEFT JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id WHERE slug = '%s' AND tt.taxonomy IN ('%s') LIMIT 1", esc_sql( $slug ), implode( "','", array_keys( $available_taxonomies ) ) );
$element = $wpdb->get_row( $sql );
}
// 1C. Try to get page/post
if ( empty( $element ) && ! empty( $available_post_types ) ) {
$sql = sprintf( "SELECT ID, post_title, post_type FROM {$wpdb->posts} WHERE post_name = '%s' AND post_status = 'publish' AND post_type IN ('%s') AND post_type != 'attachment' LIMIT 1", esc_sql( $slug ), implode( "','", array_keys( $available_post_types ) ) );
$element = $wpdb->get_row( $sql );
}
// 1D. Try to get post type archive
if ( empty( $element ) && ! empty( $available_post_types_archive ) && in_array( $snowball, $available_post_types_archive ) ) {
$post_type_slug = array_search( $snowball, $available_post_types_archive );
$element = get_post_type_object( $post_type_slug );
}
// 2A. When the term is found, we can add it to the breadcrumbs
if ( ! empty( $element->term_id ) ) {
$term_id = apply_filters( 'wpml_object_id', $element->term_id, $element->taxonomy, true );
$term = ( ( $element->term_id !== $term_id ) || $is_aioseo ) ? get_term( $term_id ) : $element;
// Alternative title
if ( $current_filter == 'wpseo_breadcrumb_links' ) {
$alt_title = ( ! empty( $yoast_meta_terms[ $term->taxonomy ][ $term->term_id ]['wpseo_bctitle'] ) ) ? $yoast_meta_terms[ $term->taxonomy ][ $term->term_id ]['wpseo_bctitle'] : '';
} else if ( $current_filter == 'seopress_pro_breadcrumbs_crumbs' ) {
$alt_title = get_term_meta( $term->term_id, '_seopress_robots_breadcrumbs', true );
} else if ( $current_filter == 'rank_math/frontend/breadcrumb/items' ) {
$alt_title = get_term_meta( $term->term_id, 'rank_math_breadcrumb_title', true );
}
$title = ( ! empty( $alt_title ) ) ? $alt_title : $term->name;
if ( $is_aioseo ) {
$breadcrumbs[] = array(
$breadcrumb_key_text => wp_strip_all_tags( $title ),
$breadcrumb_key_url => get_term_link( (int) $term->term_id, $term->taxonomy ),
'type' => 'taxonomy',
'subType' => 'parent',
'reference' => $term,
);
} else {
$breadcrumbs[] = array(
$breadcrumb_key_text => wp_strip_all_tags( $title ),
$breadcrumb_key_url => get_term_link( (int) $term->term_id, $term->taxonomy )
);
}
} // 2B. When the post/page is found, we can add it to the breadcrumbs
else if ( ! empty( $element->ID ) ) {
$page_id = apply_filters( 'wpml_object_id', $element->ID, $element->post_type, true );
$page = ( ( $element->ID !== $page_id ) || $is_aioseo ) ? get_post( $page_id ) : $element;
// Alternative title
if ( $current_filter == 'wpseo_breadcrumb_links' ) {
$alt_title = get_post_meta( $page->ID, '_yoast_wpseo_bctitle', true );
} else if ( $current_filter == 'seopress_pro_breadcrumbs_crumbs' ) {
$alt_title = get_post_meta( $page->ID, '_seopress_robots_breadcrumbs', true );
} else if ( $current_filter == 'rank_math/frontend/breadcrumb/items' ) {
$alt_title = get_post_meta( $page->ID, 'rank_math_breadcrumb_title', true );
}
$title = ( ! empty( $alt_title ) ) ? $alt_title : $page->post_title;
if ( $is_aioseo ) {
$breadcrumbs[] = array(
$breadcrumb_key_text => wp_strip_all_tags( $title ),
$breadcrumb_key_url => get_permalink( $page->ID ),
'type' => 'single',
'subType' => '',
'reference' => $page
);
} else {
$breadcrumbs[] = array(
$breadcrumb_key_text => wp_strip_all_tags( $title ),
$breadcrumb_key_url => get_permalink( $page->ID )
);
}
} // 2C. When the post archive is found, we can add it to the breadcrumbs
else if ( ! empty( $element->rewrite ) && ( ! empty( $element->labels->name ) ) ) {
if ( $is_aioseo ) {
$breadcrumbs[] = array(
$breadcrumb_key_text => apply_filters( 'post_type_archive_title', $element->labels->name, $element->name ),
$breadcrumb_key_url => get_post_type_archive_link( $element->name ),
'type' => 'postTypeArchive',
'subType' => '',
'reference' => $element
);
} else {
$breadcrumbs[] = array(
$breadcrumb_key_text => apply_filters( 'post_type_archive_title', $element->labels->name, $element->name ),
$breadcrumb_key_url => get_post_type_archive_link( $element->name )
);
}
}
}
// Add new links to current breadcrumbs array
if ( ! empty( $links ) && is_array( $links ) ) {
$first_element = reset( $links );
$last_element = end( $links );
$b_last_element = ( count( $links ) > 2 && ( ! is_singular() || is_home() ) ) ? prev( $links ) : "";
$breadcrumbs = ( ! empty( $breadcrumbs ) ) ? $breadcrumbs : array();
// Support RankMath/SEOPress/WooCommerce/Slim SEO/AIOSEO breadcrumbs
if ( in_array( $current_filter, array( 'wpseo_breadcrumb_links', 'rank_math/frontend/breadcrumb/items', 'seopress_pro_breadcrumbs_crumbs', 'woocommerce_get_breadcrumb', 'slim_seo_breadcrumbs_links', 'aioseo_breadcrumbs_trail' ) ) ) {
if ( $current_filter == 'slim_seo_breadcrumbs_links' ) {
$links = array_merge( array( $first_element ), $breadcrumbs );
} // Append the element before the last element if the last breadcrumb does not have a URL set (e.g. if the /page/ endpoint is used)
else if ( ! in_array( $current_filter, array( 'aioseo_breadcrumbs_trail', 'slim_seo_breadcrumbs_links' ) ) && ! empty( $wp->query_vars['paged'] ) && $wp->query_vars['paged'] > 1 && ! empty( $b_last_element[ $breadcrumb_key_url ] ) ) {
$links = array_merge( array( $first_element ), $breadcrumbs, array( $b_last_element ), array( $last_element ) );
} else {
$links = array_merge( array( $first_element ), $breadcrumbs, array( $last_element ) );
}
} // Support Avia/Enfold breadcrumbs
else if ( $current_filter == 'avia_breadcrumbs_trail' ) {
foreach ( $breadcrumbs as &$breadcrumb ) {
if ( isset( $breadcrumb[ $breadcrumb_key_text ] ) ) {
$breadcrumb = sprintf( '<a href="%s" title="%2$s">%2$s</a>', esc_attr( $breadcrumb[ $breadcrumb_key_url ] ), esc_attr( $breadcrumb[ $breadcrumb_key_text ] ) );
}
}
$links = array_merge( array( $first_element ), $breadcrumbs, array( 'trail_end' => $last_element ) );
}
}
return array_filter( $links );
}
}
@@ -0,0 +1,444 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WooCommerce plugins integration
*/
class Permalink_Manager_WooCommerce {
public function __construct() {
add_action( 'init', array( $this, 'init_hooks' ), 99 );
add_action( 'plugins_loaded', array( $this, 'init_early_hooks' ), 99 );
}
/**
* Add support for SEO plugins using their hooks
*/
function init_hooks() {
if ( class_exists( 'WooCommerce' ) ) {
add_filter( 'permalink_manager_filter_query', array( $this, 'woocommerce_detect' ), 8, 5 );
add_filter( 'template_redirect', array( $this, 'woocommerce_checkout_fix' ), 9 );
add_filter( 'permalink_manager_permastructs_fields', array( $this, 'woocommerce_permastructs_fields' ), 9 );
if ( class_exists( 'Permalink_Manager_Pro_Functions' ) ) {
if ( empty( $permalink_manager_options['general']['partial_disable']['post_types'] ) || ! in_array( 'shop_coupon', $permalink_manager_options['general']['partial_disable']['post_types'] ) ) {
if ( is_admin() ) {
add_filter( 'woocommerce_coupon_data_tabs', 'Permalink_Manager_Pro_Functions::woocommerce_coupon_tabs' );
add_action( 'woocommerce_coupon_data_panels', 'Permalink_Manager_Pro_Functions::woocommerce_coupon_panel' );
add_action( 'woocommerce_coupon_options_save', 'Permalink_Manager_Pro_Functions::woocommerce_save_coupon_uri', 9, 2 );
}
add_filter( 'request', 'Permalink_Manager_Pro_Functions::woocommerce_detect_coupon_code', 1, 1 );
}
} else {
add_filter( 'permalink_manager_disabled_post_types', array( $this, 'woocommerce_coupon_uris' ), 9, 1 );
}
// WooCommerce Import/Export
add_filter( 'woocommerce_product_export_product_default_columns', array( $this, 'woocommerce_csv_custom_uri_column' ), 9 );
add_filter( 'woocommerce_product_export_product_column_custom_uri', array( $this, 'woocommerce_export_custom_uri_value' ), 9, 3 );
add_filter( 'woocommerce_csv_product_import_mapping_options', array( $this, 'woocommerce_csv_custom_uri_column' ), 9 );
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', array( $this, 'woocommerce_csv_custom_uri_column' ), 9 );
add_action( 'woocommerce_product_import_inserted_product_object', array( $this, 'woocommerce_csv_import_custom_uri' ), 9, 2 );
add_action( 'woocommerce_product_duplicate', array( $this, 'woocommerce_generate_permalinks_after_duplicate' ), 9, 2 );
add_filter( 'permalink_manager_filter_default_post_uri', array( $this, 'woocommerce_product_attributes' ), 5, 5 );
if ( wp_doing_ajax() && class_exists( 'SitePress' ) ) {
add_filter( 'permalink_manager_filter_final_post_permalink', array( $this, 'woocommerce_translate_ajax_fragments_urls' ), 9999, 3 );
}
// Make sure that the custom permalinks are not generated prematurely
add_filter( 'woocommerce_rest_pre_insert_product_object', array( $this, 'woocommerce_delay_product_custom_permalink' ), 10, 3 );
add_action( 'woocommerce_rest_insert_product_object', array( $this, 'woocommerce_set_custom_uri_after_rest_insert' ), 20, 3 );
}
// WooCommerce Wishlist Plugin
if ( function_exists( 'tinv_get_option' ) ) {
add_filter( 'permalink_manager_detect_uri', array( $this, 'ti_woocommerce_wishlist_uris' ), 15, 3 );
}
// WooCommerce Subscriptions
if ( class_exists( 'WC_Subscriptions' ) ) {
add_filter( 'permalink_manager_filter_final_post_permalink', array( $this, 'wcs_fix_subscription_links' ), 10, 3 );
}
}
/**
* Some hooks must be called shortly after all the plugins are loaded
*/
public function init_early_hooks() {
if ( class_exists( 'WooCommerce' ) ) {
add_filter( 'woocommerce_get_endpoint_url', array( 'Permalink_Manager_Core_Functions', 'control_trailing_slashes' ), 9 );
add_action( 'before_woocommerce_init', array( $this, 'woocommerce_declare_compatibility' ) );
}
}
/**
* Fix query on WooCommerce shop page & disable the canonical redirect if WooCommerce query variables are set
*/
function woocommerce_detect( $query, $old_query, $uri_parts, $pm_query, $content_type ) {
global $woocommerce, $pm_query;
$shop_page_id = get_option( 'woocommerce_shop_page_id' );
// WPML - translate shop page id
$shop_page_id = apply_filters( 'wpml_object_id', $shop_page_id, 'page', true );
// Fix shop page
if ( get_theme_support( 'woocommerce' ) && ! empty( $pm_query['id'] ) && is_numeric( $pm_query['id'] ) && $shop_page_id == $pm_query['id'] ) {
$query['post_type'] = 'product';
unset( $query['pagename'] );
}
// Fix WooCommerce pages
if ( ! empty( $woocommerce->query->query_vars ) ) {
$query_vars = $woocommerce->query->query_vars;
foreach ( $query_vars as $key => $val ) {
if ( isset( $query[ $key ] ) ) {
$query['do_not_redirect'] = 1;
break;
}
}
}
return $query;
}
/**
* Redirects the user from the Shop archive to the Shop page if the user is not searching for anything
* Disable canonical redirect on "thank you" & another WooCommerce pages
*/
function woocommerce_checkout_fix() {
global $wp_query, $pm_query, $permalink_manager_options;
// Redirect from Shop archive to selected page
if ( is_shop() && empty( $pm_query['id'] ) ) {
$redirect_mode = ( ! empty( $permalink_manager_options['general']['redirect'] ) ) ? $permalink_manager_options['general']['redirect'] : false;
$redirect_shop = apply_filters( 'permalink_manager_redirect_shop_archive', false );
$shop_page = get_option( 'woocommerce_shop_page_id' );
if ( $redirect_mode && $redirect_shop && $shop_page && empty( $wp_query->query_vars['s'] ) ) {
$shop_url = get_permalink( $shop_page );
wp_safe_redirect( $shop_url, $redirect_mode );
exit();
}
}
if ( is_checkout() || ( function_exists( 'is_wc_endpoint_url' ) && is_wc_endpoint_url() ) ) {
$wp_query->query_vars['do_not_redirect'] = 1;
}
}
/**
* Separate WooCommerce custom post types & taxonomies in "Permastructures" screen
*
* @param array $fields
*
* @return array
*/
public function woocommerce_permastructs_fields( $fields ) {
if ( is_array( $fields ) ) {
$woocommerce_fields = array( 'product' => 'post_types', 'product_tag' => 'taxonomies', 'product_cat' => 'taxonomies', 'product_brand' => 'taxonomies' );
$woocommerce_attributes = wc_get_attribute_taxonomies();
foreach ( $woocommerce_attributes as $woocommerce_attribute ) {
$woocommerce_fields["pa_{$woocommerce_attribute->attribute_name}"] = 'taxonomies';
}
foreach ( $woocommerce_fields as $field => $field_type ) {
if ( empty( $fields[ $field_type ]["fields"][ $field ] ) ) {
continue;
}
$fields["woocommerce"]["fields"][ $field ] = $fields[ $field_type ]["fields"][ $field ];
unset( $fields[ $field_type ]["fields"][ $field ] );
}
}
return $fields;
}
/**
* Add "Coupons" to the list of excluded post types
*
* @param array $post_types
*
* @return array
*/
public function woocommerce_coupon_uris( $post_types ) {
if ( is_array( $post_types ) ) {
$post_types[] = 'shop_coupon';
}
return $post_types;
}
/**
* Generate a new custom permalink for duplicated product
*
* @param WC_Product $new_product The new product object.
* @param WC_Product $old_product The product that was duplicated.
*/
function woocommerce_generate_permalinks_after_duplicate( $new_product, $old_product ) {
if ( ! empty( $new_product ) ) {
$product_id = $new_product->get_id();
// Ignore variations
if ( $new_product->get_type() === 'variation' || Permalink_Manager_Helper_Functions::is_post_excluded( $product_id, true ) ) {
return;
}
$custom_uri = Permalink_Manager_URI_Functions_Post::get_default_post_uri( $product_id, false, true );
Permalink_Manager_URI_Functions::save_single_uri( $product_id, $custom_uri, false, true );
}
}
/**
* If the URI contains %pa_attribute_name% tag, replace it with the value of the attribute
*
* @param string $default_uri The default custom permalink that WordPress would use for the post.
* @param string $slug The post slug.
* @param WP_Post $post The post object.
* @param string $post_name The post slug.
* @param bool $native_uri true if the URI is a native URI, false if it's a custom URI
*
* @return string The default custom permalink
*/
function woocommerce_product_attributes( $default_uri, $slug, $post, $post_name, $native_uri ) {
// Do not affect native URIs
if ( $native_uri ) {
return $default_uri;
}
// Use only for products
if ( empty( $post->post_type ) || $post->post_type !== 'product' ) {
return $default_uri;
}
preg_match_all( "/%pa_(.[^\%]+)%/", $default_uri, $custom_fields );
if ( ! empty( $custom_fields[1] ) ) {
$product = wc_get_product( $post->ID );
foreach ( $custom_fields[1] as $i => $custom_field ) {
$attribute_name = sanitize_title( $custom_field );
$attribute_value = $product->get_attribute( $attribute_name );
$default_uri = str_replace( $custom_fields[0][ $i ], Permalink_Manager_Helper_Functions::sanitize_title( $attribute_value ), $default_uri );
}
}
return $default_uri;
}
/**
* Stop the plugin from generating the custom permalink for new WooCommerce product prematurely.
*
* @param WC_Data $data The product object.
* @param WP_REST_Request $request The REST API request object.
* @param bool $is_new_post Whether the product is newly created.
*/
function woocommerce_delay_product_custom_permalink( $data, $request, $is_new_post ) {
add_filter( 'permalink_manager_pre_update_post_uri', '__return_null', 100 );
return $data;
}
/**
* Sets a custom permalink for a WooCommerce product after it is inserted via the REST API.
*
* @param WC_Product $product The product object.
* @param WP_REST_Request $request The REST API request object.
* @param bool $is_new_post Whether the product is newly created.
*/
function woocommerce_set_custom_uri_after_rest_insert( $product, $request, $is_new_post ) {
if ( $is_new_post && class_exists( 'Permalink_Manager_URI_Functions_Post' ) && method_exists( $product, 'get_id' ) ) {
$product_id = $product->get_id();
remove_filter( 'permalink_manager_pre_update_post_uri', '__return_null', 100 );
$new_uri = Permalink_Manager_URI_Functions_Post::get_default_post_uri( $product_id );
Permalink_Manager_URI_Functions_Post::save_uri( $product_id, $new_uri, $is_new_post );
}
}
/**
* Check the current request is a WooCommerce AJAX request. If it is, check the translated page's URL should be returned
*
* @param string $permalink The full URL of the post
* @param WP_Post $post The post object
* @param string $old_permalink The original URL of the post.
*
* @return string The permalink is being returned.
*/
function woocommerce_translate_ajax_fragments_urls( $permalink, $post, $old_permalink ) {
// Use it only if the permalinks are different
if ( $permalink == $old_permalink || $post->post_type !== 'page' ) {
return $permalink;
}
// A. Native WooCommerce AJAX events
if ( ! empty( $_REQUEST['wc-ajax'] ) ) {
$action = sanitize_title( $_REQUEST['wc-ajax'] );
} // B. Shoptimizer theme
else if ( ! empty( $_REQUEST['action'] ) ) {
$action = sanitize_title( $_REQUEST['action'] );
}
// Allowed action names
$allowed_actions = array( 'shoptimizer_pdp_ajax_atc', 'get_refreshed_fragments' );
if ( ! empty( $action ) && in_array( $action, $allowed_actions ) ) {
$translated_post_id = apply_filters( 'wpml_object_id', $post->ID, 'page' );
$permalink = ( $translated_post_id !== $post->ID ) ? get_permalink( $translated_post_id ) : $permalink;
}
return $permalink;
}
/**
* 4FA. Add a new column to the WooCommerce CSV Import/Export tool
*
* @param array $columns The array of columns to be displayed.
*
* @return array The $columns array.
*/
function woocommerce_csv_custom_uri_column( $columns ) {
if ( ! is_array( $columns ) ) {
return $columns;
}
$label = __( 'Custom URI', 'permalink-manager' );
$key = 'custom_uri';
if ( current_filter() == 'woocommerce_csv_product_import_mapping_default_columns' ) {
$columns[ $label ] = $key;
} else {
$columns[ $key ] = $label;
}
return $columns;
}
/**
* 4FB. Return the custom permalink of the product if it exists, otherwise return the default URI
*
* @param string $value The value of the column.
* @param WC_Product $product The product object.
* @param mixed $column_id The column ID.
*
* @return string The custom permalink or default permalink
*/
function woocommerce_export_custom_uri_value( $value, $product, $column_id ) {
if ( empty( $value ) && ! empty( $product ) ) {
$product_id = $product->get_id();
// Get custom permalink or default permalink
$value = Permalink_Manager_URI_Functions_Post::get_post_uri( $product_id );
}
return $value;
}
/**
* 4FC. Set the custom URI for the product using the value from CSV file, if not set use the default permalink
*
* @param WC_Product $product The product object.
* @param array $data The data array for the current row being imported.
*/
function woocommerce_csv_import_custom_uri( $product, $data ) {
if ( ! empty( $product ) ) {
$product_id = $product->get_id();
// Ignore variations
if ( $product->get_type() == 'variation' ) {
return;
}
$current_uri = Permalink_Manager_URI_Functions::get_single_uri( $product_id, false, true );
// A. Use default permalink if "Custom URI" is not set and did not exist before
if ( empty( $current_uri ) && empty( $data['custom_uri'] ) ) {
$custom_uri = Permalink_Manager_URI_Functions_Post::get_default_post_uri( $product_id, false, true );
} else if ( ! empty( $data['custom_uri'] ) ) {
$custom_uri = Permalink_Manager_Helper_Functions::sanitize_title( $data['custom_uri'] );
} else {
return;
}
Permalink_Manager_URI_Functions::save_single_uri( $product_id, $custom_uri, false, true );
}
}
/**
* Declare support for 'High-Performance order storage (COT)' and other features in WooCommerce
*/
function woocommerce_declare_compatibility() {
$features_util_class = '\Automattic\WooCommerce\Utilities\FeaturesUtil';
if ( class_exists( $features_util_class ) && method_exists( $features_util_class, 'declare_compatibility' ) ) {
$features = method_exists( $features_util_class, 'get_features' ) ? $features_util_class::get_features( true ) : array();
foreach ( array_keys( $features ) as $feature ) {
$features_util_class::declare_compatibility( $feature, PERMALINK_MANAGER_BASENAME );
}
}
}
/**
* Extract the Wishlist ID from the URI and add it to the $uri_parts array (WooCommerce Wishlist Plugin)
*
* @param array $uri_parts An array of the URI parts.
* @param string $request_url The URL that was requested.
* @param array $endpoints An array of all the endpoints that are currently registered.
*
* @return array The URI parts.
*/
function ti_woocommerce_wishlist_uris( $uri_parts, $request_url, $endpoints ) {
$wishlist_pid = function_exists( 'tinv_get_option' ) ? tinv_get_option( 'general', 'page_wishlist' ) : '';
// Find the Wishlist page URI
if ( is_numeric( $wishlist_pid ) ) {
$current_uri = Permalink_Manager_URI_Functions::get_single_uri( $wishlist_pid, false, true );
if ( ! empty( $current_uri ) ) {
$wishlist_uri = preg_quote( $current_uri, '/' );
// Extract the Wishlist ID
preg_match( "/^({$wishlist_uri})\/([^\/]+)\/?$/", $uri_parts['uri'], $output_array );
if ( ! empty( $output_array[2] ) ) {
$uri_parts['uri'] = $output_array[1];
$uri_parts['endpoint'] = 'tinvwlID';
$uri_parts['endpoint_value'] = $output_array[2];
}
}
}
return $uri_parts;
}
/**
* Keep the query strings appended to the product permalinks by WooCommerce Subscriptions
*
* @param string $permalink
* @param WP_Post $post
* @param string $old_permalink
*
* @return string
*/
function wcs_fix_subscription_links( $permalink, $post, $old_permalink ) {
if ( ! empty( $post->post_type ) && $post->post_type == 'product' && strpos( $old_permalink, 'switch-subscription=' ) !== false ) {
$query_arg = parse_url( $old_permalink, PHP_URL_QUERY );
$permalink = "{$permalink}?{$query_arg}";
}
return $permalink;
}
}