PluginProbe ʕ •ᴥ•ʔ
Permalink Manager Lite / trunk
Permalink Manager Lite vtrunk
2.5.4 2.5.3.4 2.2.18 2.2.19.2 2.2.19.3 2.2.19.3.1 2.2.2 2.2.20 2.2.20.1 2.2.20.3 2.2.4 2.2.5 2.2.6 2.2.7.2 2.2.7.3 2.2.7.5 2.2.7.6 2.2.8.4 2.2.8.5 2.2.8.6 2.2.8.7 2.2.8.9 2.2.9.1 2.2.9.2 2.2.9.2.1 2.2.9.3 2.2.9.4 2.2.9.6 2.2.9.7 2.2.9.9 2.3.0 2.3.1.1 2.4.0 2.4.1 2.4.1.2 2.4.1.3 2.4.1.4 2.4.1.5 2.4.1.6 2.4.2 2.4.2.1 2.4.3 2.4.3.1 2.4.3.2 2.4.3.3 2.4.3.4 2.4.4 2.4.4.1 2.4.4.2 2.4.4.3 2.5.0 2.5.1 2.5.1.1 2.5.1.2 2.5.1.3 2.5.1.4 2.5.2 2.5.2.1 2.5.2.2 2.5.2.3 2.5.2.4 2.5.3 2.5.3.1 2.5.3.2 2.5.3.3 trunk 0.2 0.3 0.3.1 0.3.2 0.3.3 0.3.4 0.4 0.4.1 0.4.2 0.4.3 0.4.4 0.4.6 0.4.7 0.4.8 0.4.9 0.5.3 0.5.4 1.0.0 1.0.1 1.0.4 1.1.0 1.1.1 1.1.2 1.11.6.3 2.0.0 2.0.3 2.0.4 2.0.4.3 2.0.5.1 2.0.5.2 2.0.5.3 2.0.5.3.1 2.0.5.4 2.0.5.4a 2.0.5.5 2.0.5.6 2.0.5.6.1 2.0.5.7 2.0.5.9a 2.0.6.2.1 2.0.6.2a 2.0.6.3 2.1.0 2.1.1 2.1.2.4 2.2.0 2.2.1.1 2.2.1.2 2.2.11 2.2.12 2.2.13.1 2.2.14 2.2.15.1 2.2.16 2.2.17
permalink-manager / includes / integrations / permalink-manager-seo-plugins.php
permalink-manager / includes / integrations Last commit date
permalink-manager-language-plugins.php 1 day ago permalink-manager-seo-plugins.php 1 day ago permalink-manager-third-parties.php 1 day ago permalink-manager-woocommerce.php 1 day ago
permalink-manager-seo-plugins.php
358 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 /**
8 * SEO plugins integration
9 */
10 class Permalink_Manager_SEO_Plugins {
11
12 public function __construct() {
13 add_action( 'init', array( $this, 'init_hooks' ), 99 );
14 }
15
16 /**
17 * Add support for SEO plugins using their hooks
18 */
19 function init_hooks() {
20 // Yoast SEO
21 add_filter( 'wpseo_xml_sitemap_post_url', array( $this, 'yoast_fix_sitemap_urls' ), 9 );
22 if ( defined( 'WPSEO_VERSION' ) && version_compare( WPSEO_VERSION, '14.0', '>=' ) ) {
23 add_action( 'permalink_manager_updated_post_uri', array( $this, 'yoast_update_indexable_permalink' ), 10, 3 );
24 add_action( 'permalink_manager_updated_term_uri', array( $this, 'yoast_update_indexable_permalink' ), 10, 3 );
25 add_filter( 'wpseo_canonical', array( $this, 'yoast_fix_canonical' ), 10 );
26 add_filter( 'wpseo_opengraph_url', array( $this, 'yoast_fix_canonical' ), 10 );
27 add_filter( 'wpseo_dynamic_permalinks_enabled', '__return_true', 5 );
28 }
29
30 // Breadcrumbs
31 add_filter( 'wpseo_breadcrumb_links', array( $this, 'filter_breadcrumbs' ), 9 );
32 add_filter( 'rank_math/frontend/breadcrumb/items', array( $this, 'filter_breadcrumbs' ), 9 );
33 add_filter( 'seopress_pro_breadcrumbs_crumbs', array( $this, 'filter_breadcrumbs' ), 9 );
34 add_filter( 'woocommerce_get_breadcrumb', array( $this, 'filter_breadcrumbs' ), 9 );
35 add_filter( 'slim_seo_breadcrumbs_links', array( $this, 'filter_breadcrumbs' ), 9 );
36 add_filter( 'aioseo_breadcrumbs_trail', array( $this, 'filter_breadcrumbs' ), 9 );
37 add_filter( 'avia_breadcrumbs_trail', array( $this, 'filter_breadcrumbs' ), 100 );
38 }
39
40 /**
41 * Get the HTTP protocol of the home URL and use it in Yoast SEO sitemap permalinks
42 *
43 * @param string $permalink The permalink in the sitemap
44 *
45 * @return string The sitemap's permalink
46 */
47 function yoast_fix_sitemap_urls( $permalink ) {
48 if ( class_exists( 'WPSEO_Utils' ) ) {
49 $home_url = WPSEO_Utils::home_url();
50 $home_protocol = wp_parse_url( $home_url, PHP_URL_SCHEME );
51
52 // Use the built-in WordPress function to safely swap the protocol
53 $permalink = set_url_scheme( $permalink, $home_protocol );
54 }
55
56 return $permalink;
57 }
58
59 /**
60 * Update the permalink in the Yoast SEO indexable table when the permalink is changed
61 *
62 * @param int $element_id The ID of the post/term element that was updated.
63 * @param string $new_uri The new URI of the element.
64 * @param string $old_uri The old URI of the element.
65 */
66 function yoast_update_indexable_permalink( $element_id, $new_uri, $old_uri ) {
67 global $wpdb;
68
69 if ( ! empty( $new_uri ) && ! empty( $old_uri ) && $new_uri !== $old_uri ) {
70 if ( current_filter() == 'permalink_manager_updated_term_uri' ) {
71 $permalink = get_term_link( (int) $element_id );
72 $object_type = 'term';
73 } else {
74 $permalink = get_permalink( $element_id );
75 $object_type = 'post';
76 }
77
78 if ( ! empty( $permalink ) && ! is_wp_error( $permalink ) ) {
79 $permalink_hash = strlen( $permalink ) . ':' . md5( $permalink );
80
81 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Updating a custom 3rd party table where core functions cannot be used.
82 $wpdb->update( "{$wpdb->prefix}yoast_indexable", array(
83 'permalink' => $permalink,
84 'permalink_hash' => $permalink_hash
85 ), array(
86 'object_id' => $element_id,
87 'object_type' => $object_type
88 ), array( '%s', '%s' ), array( '%d', '%s' ) );
89 }
90 }
91 }
92
93 /**
94 * Filter the canonical permalink used by SEO using 'wpseo_canonical' & 'wpseo_opengraph_url' hooks
95 *
96 * @param string $url The canonical URL that Yoast SEO has generated.
97 *
98 * @return string the URL.
99 */
100 function yoast_fix_canonical( $url ) {
101 global $pm_query, $wp_rewrite;
102
103 if ( ! empty( $pm_query['id'] ) ) {
104 $element = get_queried_object();
105
106 if ( ! empty( $element->ID ) && ! empty( $element->post_type ) ) {
107 $new_url = get_permalink( $element->ID );
108
109 // Do not filter if custom canonical URL is set
110 $yoast_canonical_url = get_post_meta( $element->ID, '_yoast_wpseo_canonical', true );
111 if ( ! empty( $yoast_canonical_url ) ) {
112 return $url;
113 }
114
115 if ( is_home() ) {
116 $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
117 $new_url = ( $paged > 1 ) ? sprintf( '%s/%s/%d', trim( $new_url, '/' ), $wp_rewrite->pagination_base, $paged ) : $new_url;
118 } else {
119 $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;
120 $new_url = ( $paged > 1 ) ? sprintf( '%s/%d', trim( $new_url, '/' ), $paged ) : $new_url;
121 }
122 } else if ( ! empty( $element->taxonomy ) && ! empty( $element->term_id ) ) {
123 $new_url = get_term_link( $element, $element->taxonomy );
124
125 // Do not filter if custom canonical URL is set
126 if ( class_exists( 'WPSEO_Taxonomy_Meta' ) ) {
127 $yoast_canonical_url = WPSEO_Taxonomy_Meta::get_term_meta( $element, $element->taxonomy, 'canonical' );
128 if ( ! empty( $yoast_canonical_url ) ) {
129 return $url;
130 }
131 }
132
133 $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
134 if ( $paged > 1 ) {
135 $new_url = sprintf( '%s/%s/%d', trim( $new_url, '/' ), $wp_rewrite->pagination_base, $paged );
136 }
137 }
138
139 $url = ( ! empty( $new_url ) ) ? $new_url : $url;
140 $url = Permalink_Manager_Core_Functions::control_trailing_slashes( $url );
141 }
142
143 return $url;
144 }
145
146 /**
147 * Filter the breadcrumbs array to match the structure of currently requested URL
148 *
149 * @param array $links The current breadcrumb links.
150 *
151 * @return array The $links array.
152 */
153 function filter_breadcrumbs( $links ) {
154 // Get post type permastructure settings
155 global $permalink_manager_options, $post, $wpdb, $wp, $wp_current_filter;
156
157 // Check if the filter should be activated
158 if ( empty( $permalink_manager_options['general']['yoast_breadcrumbs'] ) ) {
159 return $links;
160 }
161
162 // Get current post/page/term (if available)
163 $queried_element = get_queried_object();
164 if ( ! empty( $queried_element->ID ) ) {
165 $element_id = $queried_element->ID;
166 } else if ( ! empty( $queried_element->term_id ) ) {
167 $element_id = "tax-{$queried_element->term_id}";
168 } else if ( defined( 'REST_REQUEST' ) && ! empty( $post->ID ) ) {
169 $element_id = $post->ID;
170 }
171
172 // Get the custom permalink (if available) or the current request URL (if unavailable)
173 $custom_uri = ( ! empty( $element_id ) ) ? Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null ) : '';
174
175 if ( ! empty( $custom_uri ) ) {
176 $custom_uri = preg_replace( "/([^\/]+)$/", '', $custom_uri );
177 } else {
178 return $links;
179 }
180
181 $custom_uri_parts = explode( '/', trim( $custom_uri ) );
182 $breadcrumbs = array();
183 $snowball = '';
184 $available_taxonomies = Permalink_Manager_Helper_Functions::get_taxonomies_array( null, null, true );
185 $available_post_types = Permalink_Manager_Helper_Functions::get_post_types_array( null, null, true );
186 $available_post_types_archive = Permalink_Manager_Helper_Functions::get_post_types_array( 'archive_slug', null, true );
187 $current_filter = end( $wp_current_filter );
188
189 // Get Yoast Meta (the breadcrumbs titles can be changed in Yoast metabox)
190 $yoast_meta_terms = get_option( 'wpseo_taxonomy_meta' );
191
192 // Check what array keys should be used for breadcrumbs ("All In One SEO" uses a more complicated schema)
193 if ( $current_filter == 'aioseo_breadcrumbs_trail' ) {
194 $breadcrumb_key_text = 'label';
195 $breadcrumb_key_url = 'link';
196 $is_aioseo = true;
197 } else if ( in_array( $current_filter, array( 'wpseo_breadcrumb_links', 'slim_seo_breadcrumbs_links' ) ) ) {
198 $breadcrumb_key_text = 'text';
199 $breadcrumb_key_url = 'url';
200 $is_aioseo = false;
201 } else {
202 $breadcrumb_key_text = 0;
203 $breadcrumb_key_url = 1;
204 $is_aioseo = false;
205 }
206
207 // Get internal breadcrumb elements
208 foreach ( $custom_uri_parts as $slug ) {
209 if ( empty( $slug ) ) {
210 continue;
211 }
212
213 $snowball = ( empty( $snowball ) ) ? $slug : "{$snowball}/{$slug}";
214
215 // 1A. Try to match any custom URI
216 $uri = trim( $snowball, "/" );
217 $element = Permalink_Manager_URI_Functions::find_uri( $uri, true );
218
219 if ( ! empty( $element ) && strpos( $element, 'tax-' ) !== false ) {
220 $element_id = intval( preg_replace( "/[^0-9]/", "", $element ) );
221 $element = get_term( $element_id );
222 } else if ( is_numeric( $element ) ) {
223 $element = get_post( $element );
224 }
225
226 // 1B. Try to get term
227 if ( empty( $element ) && ! empty( $available_taxonomies ) ) {
228 $tax_in_clause = \Permalink_Manager_Helper_Functions::prepare_array_for_sql_in( array_keys( $available_taxonomies ) );
229
230 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Safely escaped by helper function.
231 $element = $wpdb->get_row( $wpdb->prepare( "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 t.slug = %s AND tt.taxonomy IN ($tax_in_clause) LIMIT 1", $slug ) );
232 }
233
234 // 1C. Try to get page/post
235 if ( empty( $element ) && ! empty( $available_post_types ) ) {
236 $cpt_in_clause = \Permalink_Manager_Helper_Functions::prepare_array_for_sql_in( array_keys( $available_post_types ) );
237
238 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Safely escaped by helper function.
239 $element = $wpdb->get_row( $wpdb->prepare( "SELECT ID, post_title, post_type FROM {$wpdb->posts} WHERE post_name = %s AND post_status = 'publish' AND post_type IN ($cpt_in_clause) AND post_type != 'attachment' LIMIT 1", $slug ) );
240 }
241
242 // 1D. Try to get post type archive
243 if ( empty( $element ) && ! empty( $available_post_types_archive ) && in_array( $snowball, $available_post_types_archive ) ) {
244 $post_type_slug = array_search( $snowball, $available_post_types_archive );
245 $element = get_post_type_object( $post_type_slug );
246 }
247
248 // 2A. When the term is found, we can add it to the breadcrumbs
249 if ( ! empty( $element->term_id ) ) {
250 $term_id = apply_filters( 'wpml_object_id', $element->term_id, $element->taxonomy, true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
251 $term = ( ( $element->term_id !== $term_id ) || $is_aioseo ) ? get_term( $term_id ) : $element;
252
253 // Alternative title
254 if ( $current_filter == 'wpseo_breadcrumb_links' ) {
255 $alt_title = ( ! empty( $yoast_meta_terms[ $term->taxonomy ][ $term->term_id ]['wpseo_bctitle'] ) ) ? $yoast_meta_terms[ $term->taxonomy ][ $term->term_id ]['wpseo_bctitle'] : '';
256 } else if ( $current_filter == 'seopress_pro_breadcrumbs_crumbs' ) {
257 $alt_title = get_term_meta( $term->term_id, '_seopress_robots_breadcrumbs', true );
258 } else if ( $current_filter == 'rank_math/frontend/breadcrumb/items' ) {
259 $alt_title = get_term_meta( $term->term_id, 'rank_math_breadcrumb_title', true );
260 }
261
262 $title = ( ! empty( $alt_title ) ) ? $alt_title : $term->name;
263
264 if ( $is_aioseo ) {
265 $breadcrumbs[] = array(
266 $breadcrumb_key_text => wp_strip_all_tags( $title ),
267 $breadcrumb_key_url => get_term_link( (int) $term->term_id, $term->taxonomy ),
268 'type' => 'taxonomy',
269 'subType' => 'parent',
270 'reference' => $term,
271 );
272 } else {
273 $breadcrumbs[] = array(
274 $breadcrumb_key_text => wp_strip_all_tags( $title ),
275 $breadcrumb_key_url => get_term_link( (int) $term->term_id, $term->taxonomy )
276 );
277 }
278 } // 2B. When the post/page is found, we can add it to the breadcrumbs
279 else if ( ! empty( $element->ID ) ) {
280 $page_id = apply_filters( 'wpml_object_id', $element->ID, $element->post_type, true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
281 $page = ( ( $element->ID !== $page_id ) || $is_aioseo ) ? get_post( $page_id ) : $element;
282
283 // Alternative title
284 if ( $current_filter == 'wpseo_breadcrumb_links' ) {
285 $alt_title = get_post_meta( $page->ID, '_yoast_wpseo_bctitle', true );
286 } else if ( $current_filter == 'seopress_pro_breadcrumbs_crumbs' ) {
287 $alt_title = get_post_meta( $page->ID, '_seopress_robots_breadcrumbs', true );
288 } else if ( $current_filter == 'rank_math/frontend/breadcrumb/items' ) {
289 $alt_title = get_post_meta( $page->ID, 'rank_math_breadcrumb_title', true );
290 }
291
292 $title = ( ! empty( $alt_title ) ) ? $alt_title : $page->post_title;
293
294 if ( $is_aioseo ) {
295 $breadcrumbs[] = array(
296 $breadcrumb_key_text => wp_strip_all_tags( $title ),
297 $breadcrumb_key_url => get_permalink( $page->ID ),
298 'type' => 'single',
299 'subType' => '',
300 'reference' => $page
301 );
302 } else {
303 $breadcrumbs[] = array(
304 $breadcrumb_key_text => wp_strip_all_tags( $title ),
305 $breadcrumb_key_url => get_permalink( $page->ID )
306 );
307 }
308 } // 2C. When the post archive is found, we can add it to the breadcrumbs
309 else if ( ! empty( $element->rewrite ) && ( ! empty( $element->labels->name ) ) ) {
310 if ( $is_aioseo ) {
311 $breadcrumbs[] = array(
312 $breadcrumb_key_text => apply_filters( 'post_type_archive_title', $element->labels->name, $element->name ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
313 $breadcrumb_key_url => get_post_type_archive_link( $element->name ),
314 'type' => 'postTypeArchive',
315 'subType' => '',
316 'reference' => $element
317 );
318 } else {
319 $breadcrumbs[] = array(
320 $breadcrumb_key_text => apply_filters( 'post_type_archive_title', $element->labels->name, $element->name ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
321 $breadcrumb_key_url => get_post_type_archive_link( $element->name )
322 );
323 }
324 }
325 }
326
327 // Add new links to current breadcrumbs array
328 if ( ! empty( $links ) && is_array( $links ) ) {
329 $first_element = reset( $links );
330 $last_element = end( $links );
331 $b_last_element = ( count( $links ) > 2 && ( ! is_singular() || is_home() ) ) ? prev( $links ) : "";
332 $breadcrumbs = ( ! empty( $breadcrumbs ) ) ? $breadcrumbs : array();
333
334 // Support RankMath/SEOPress/WooCommerce/Slim SEO/AIOSEO breadcrumbs
335 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' ) ) ) {
336 if ( $current_filter == 'slim_seo_breadcrumbs_links' ) {
337 $links = array_merge( array( $first_element ), $breadcrumbs );
338 } // 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)
339 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 ] ) ) {
340 $links = array_merge( array( $first_element ), $breadcrumbs, array( $b_last_element ), array( $last_element ) );
341 } else {
342 $links = array_merge( array( $first_element ), $breadcrumbs, array( $last_element ) );
343 }
344 } // Support Avia/Enfold breadcrumbs
345 else if ( $current_filter == 'avia_breadcrumbs_trail' ) {
346 foreach ( $breadcrumbs as &$breadcrumb ) {
347 if ( isset( $breadcrumb[ $breadcrumb_key_text ] ) ) {
348 $breadcrumb = sprintf( '<a href="%s" title="%2$s">%2$s</a>', esc_attr( $breadcrumb[ $breadcrumb_key_url ] ), esc_attr( $breadcrumb[ $breadcrumb_key_text ] ) );
349 }
350 }
351
352 $links = array_merge( array( $first_element ), $breadcrumbs, array( 'trail_end' => $last_element ) );
353 }
354 }
355
356 return array_filter( $links );
357 }
358 }