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-woocommerce.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-woocommerce.php
452 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 /**
8 * WooCommerce plugins integration
9 */
10 class Permalink_Manager_WooCommerce {
11
12 public function __construct() {
13 add_action( 'init', array( $this, 'init_hooks' ), 99 );
14 add_action( 'plugins_loaded', array( $this, 'init_early_hooks' ), 99 );
15 }
16
17 /**
18 * Add support for SEO plugins using their hooks
19 */
20 function init_hooks() {
21 global $permalink_manager_options;
22
23 if ( class_exists( 'WooCommerce' ) ) {
24 add_filter( 'permalink_manager_filter_query', array( $this, 'woocommerce_detect' ), 8, 5 );
25 add_filter( 'template_redirect', array( $this, 'woocommerce_checkout_fix' ), 9 );
26 add_filter( 'permalink_manager_permastructs_fields', array( $this, 'woocommerce_permastructs_fields' ), 9 );
27
28 if ( class_exists( 'Permalink_Manager_Pro_Functions' ) ) {
29 if ( empty( $permalink_manager_options['general']['partial_disable']['post_types'] ) || ! in_array( 'shop_coupon', $permalink_manager_options['general']['partial_disable']['post_types'] ) ) {
30 if ( is_admin() ) {
31 add_filter( 'woocommerce_coupon_data_tabs', 'Permalink_Manager_Pro_Functions::woocommerce_coupon_tabs' );
32 add_action( 'woocommerce_coupon_data_panels', 'Permalink_Manager_Pro_Functions::woocommerce_coupon_panel' );
33 add_action( 'woocommerce_coupon_options_save', 'Permalink_Manager_Pro_Functions::woocommerce_save_coupon_uri', 9, 2 );
34 }
35
36 add_filter( 'request', 'Permalink_Manager_Pro_Functions::woocommerce_detect_coupon_code', 1, 1 );
37 }
38 } else {
39 add_filter( 'permalink_manager_disabled_post_types', array( $this, 'woocommerce_coupon_uris' ), 9, 1 );
40 }
41
42 // WooCommerce Import/Export
43 add_filter( 'woocommerce_product_export_product_default_columns', array( $this, 'woocommerce_csv_custom_uri_column' ), 9 );
44 add_filter( 'woocommerce_product_export_product_column_custom_uri', array( $this, 'woocommerce_export_custom_uri_value' ), 9, 3 );
45
46 add_filter( 'woocommerce_csv_product_import_mapping_options', array( $this, 'woocommerce_csv_custom_uri_column' ), 9 );
47 add_filter( 'woocommerce_csv_product_import_mapping_default_columns', array( $this, 'woocommerce_csv_custom_uri_column' ), 9 );
48 add_action( 'woocommerce_product_import_inserted_product_object', array( $this, 'woocommerce_csv_import_custom_uri' ), 9, 2 );
49
50 add_action( 'woocommerce_product_duplicate', array( $this, 'woocommerce_generate_permalinks_after_duplicate' ), 9, 2 );
51 add_filter( 'permalink_manager_filter_default_post_uri', array( $this, 'woocommerce_product_attributes' ), 5, 5 );
52
53 if ( wp_doing_ajax() && class_exists( 'SitePress' ) ) {
54 add_filter( 'permalink_manager_filter_final_post_permalink', array( $this, 'woocommerce_translate_ajax_fragments_urls' ), 9999, 3 );
55 }
56
57 // Make sure that the custom permalinks are not generated prematurely
58 add_filter( 'woocommerce_rest_pre_insert_product_object', array( $this, 'woocommerce_delay_product_custom_permalink' ), 10, 3 );
59 add_action( 'woocommerce_rest_insert_product_object', array( $this, 'woocommerce_set_custom_uri_after_rest_insert' ), 20, 3 );
60 }
61
62 // WooCommerce Wishlist Plugin
63 if ( function_exists( 'tinv_get_option' ) ) {
64 add_filter( 'permalink_manager_detect_uri', array( $this, 'ti_woocommerce_wishlist_uris' ), 15, 3 );
65 }
66
67 // WooCommerce Subscriptions
68 if ( class_exists( 'WC_Subscriptions' ) ) {
69 add_filter( 'permalink_manager_filter_final_post_permalink', array( $this, 'wcs_fix_subscription_links' ), 10, 3 );
70 }
71 }
72
73 /**
74 * Some hooks must be called shortly after all the plugins are loaded
75 */
76 public function init_early_hooks() {
77 if ( class_exists( 'WooCommerce' ) ) {
78 add_filter( 'woocommerce_get_endpoint_url', array( 'Permalink_Manager_Core_Functions', 'control_trailing_slashes' ), 9 );
79 add_action( 'before_woocommerce_init', array( $this, 'woocommerce_declare_compatibility' ) );
80 }
81 }
82
83 /**
84 * Fix query on WooCommerce shop page & disable the canonical redirect if WooCommerce query variables are set
85 */
86 function woocommerce_detect( $query, $old_query, $uri_parts, $pm_query, $content_type ) {
87 global $woocommerce, $pm_query;
88
89 $shop_page_id = get_option( 'woocommerce_shop_page_id' );
90
91 // WPML - translate shop page id
92 $shop_page_id = apply_filters( 'wpml_object_id', $shop_page_id, 'page', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
93
94 // Fix shop page
95 if ( get_theme_support( 'woocommerce' ) && ! empty( $pm_query['id'] ) && is_numeric( $pm_query['id'] ) && $shop_page_id == $pm_query['id'] ) {
96 $query['post_type'] = 'product';
97 unset( $query['pagename'] );
98 }
99
100 // Fix WooCommerce pages
101 if ( ! empty( $woocommerce->query->query_vars ) ) {
102 $query_vars = $woocommerce->query->query_vars;
103
104 foreach ( $query_vars as $key => $val ) {
105 if ( isset( $query[ $key ] ) ) {
106 $query['do_not_redirect'] = 1;
107 break;
108 }
109 }
110 }
111
112 return $query;
113 }
114
115 /**
116 * Redirects the user from the Shop archive to the Shop page if the user is not searching for anything
117 * Disable canonical redirect on "thank you" & another WooCommerce pages
118 */
119 function woocommerce_checkout_fix() {
120 global $wp_query, $pm_query, $permalink_manager_options;
121
122 // Redirect from Shop archive to selected page
123 if ( is_shop() && empty( $pm_query['id'] ) ) {
124 $redirect_mode = ( ! empty( $permalink_manager_options['general']['redirect'] ) ) ? $permalink_manager_options['general']['redirect'] : false;
125 $redirect_shop = apply_filters( 'permalink_manager_redirect_shop_archive', false );
126 $shop_page = get_option( 'woocommerce_shop_page_id' );
127
128 if ( $redirect_mode && $redirect_shop && $shop_page && empty( $wp_query->query_vars['s'] ) ) {
129 $shop_url = get_permalink( $shop_page );
130 wp_safe_redirect( $shop_url, $redirect_mode );
131 exit();
132 }
133 }
134
135 if ( is_checkout() || ( function_exists( 'is_wc_endpoint_url' ) && is_wc_endpoint_url() ) ) {
136 $wp_query->query_vars['do_not_redirect'] = 1;
137 }
138 }
139
140 /**
141 * Separate WooCommerce custom post types & taxonomies in "Permastructures" screen
142 *
143 * @param array $fields
144 *
145 * @return array
146 */
147 public function woocommerce_permastructs_fields( $fields ) {
148 if ( is_array( $fields ) ) {
149 $woocommerce_fields = array( 'product' => 'post_types', 'product_tag' => 'taxonomies', 'product_cat' => 'taxonomies', 'product_brand' => 'taxonomies' );
150 $woocommerce_attributes = wc_get_attribute_taxonomies();
151
152 foreach ( $woocommerce_attributes as $woocommerce_attribute ) {
153 $woocommerce_fields["pa_{$woocommerce_attribute->attribute_name}"] = 'taxonomies';
154 }
155
156 foreach ( $woocommerce_fields as $field => $field_type ) {
157 if ( empty( $fields[ $field_type ]["fields"][ $field ] ) ) {
158 continue;
159 }
160
161 $fields["woocommerce"]["fields"][ $field ] = $fields[ $field_type ]["fields"][ $field ];
162 unset( $fields[ $field_type ]["fields"][ $field ] );
163 }
164 }
165
166 return $fields;
167 }
168
169 /**
170 * Add "Coupons" to the list of excluded post types
171 *
172 * @param array $post_types
173 *
174 * @return array
175 */
176 public function woocommerce_coupon_uris( $post_types ) {
177 if ( is_array( $post_types ) ) {
178 $post_types[] = 'shop_coupon';
179 }
180
181 return $post_types;
182 }
183
184 /**
185 * Generate a new custom permalink for duplicated product
186 *
187 * @param WC_Product $new_product The new product object.
188 * @param WC_Product $old_product The product that was duplicated.
189 */
190 function woocommerce_generate_permalinks_after_duplicate( $new_product, $old_product ) {
191 if ( ! empty( $new_product ) ) {
192 $product_id = $new_product->get_id();
193
194 // Ignore variations
195 if ( $new_product->get_type() === 'variation' || Permalink_Manager_Helper_Functions::is_post_excluded( $product_id, true ) ) {
196 return;
197 }
198
199 $custom_uri = Permalink_Manager_URI_Functions_Post::get_default_post_uri( $product_id, false, true );
200 Permalink_Manager_URI_Functions::save_single_uri( $product_id, $custom_uri, false, true );
201 }
202 }
203
204 /**
205 * If the URI contains %pa_attribute_name% tag, replace it with the value of the attribute
206 *
207 * @param string $default_uri The default custom permalink that WordPress would use for the post.
208 * @param string $slug The post slug.
209 * @param WP_Post $post The post object.
210 * @param string $post_name The post slug.
211 * @param bool $native_uri true if the URI is a native URI, false if it's a custom URI
212 *
213 * @return string The default custom permalink
214 */
215 function woocommerce_product_attributes( $default_uri, $slug, $post, $post_name, $native_uri ) {
216 // Do not affect native URIs
217 if ( $native_uri ) {
218 return $default_uri;
219 }
220
221 // Use only for products
222 if ( empty( $post->post_type ) || $post->post_type !== 'product' ) {
223 return $default_uri;
224 }
225
226 preg_match_all( "/%pa_(.[^\%]+)%/", $default_uri, $custom_fields );
227
228 if ( ! empty( $custom_fields[1] ) ) {
229 $product = wc_get_product( $post->ID );
230
231 foreach ( $custom_fields[1] as $i => $custom_field ) {
232 $attribute_name = sanitize_title( $custom_field );
233 $attribute_value = $product->get_attribute( $attribute_name );
234
235 $default_uri = str_replace( $custom_fields[0][ $i ], Permalink_Manager_Helper_Functions::sanitize_title( $attribute_value ), $default_uri );
236 }
237 }
238
239 return $default_uri;
240 }
241
242 /**
243 * Stop the plugin from generating the custom permalink for new WooCommerce product prematurely.
244 *
245 * @param WC_Data $data The product object.
246 * @param WP_REST_Request $request The REST API request object.
247 * @param bool $is_new_post Whether the product is newly created.
248 */
249 function woocommerce_delay_product_custom_permalink( $data, $request, $is_new_post ) {
250 add_filter( 'permalink_manager_pre_update_post_uri', '__return_null', 100 );
251
252 return $data;
253 }
254
255 /**
256 * Sets a custom permalink for a WooCommerce product after it is inserted via the REST API.
257 *
258 * @param WC_Product $product The product object.
259 * @param WP_REST_Request $request The REST API request object.
260 * @param bool $is_new_post Whether the product is newly created.
261 */
262 function woocommerce_set_custom_uri_after_rest_insert( $product, $request, $is_new_post ) {
263 if ( $is_new_post && class_exists( 'Permalink_Manager_URI_Functions_Post' ) && method_exists( $product, 'get_id' ) ) {
264 $product_id = $product->get_id();
265
266 remove_filter( 'permalink_manager_pre_update_post_uri', '__return_null', 100 );
267
268 $new_uri = Permalink_Manager_URI_Functions_Post::get_default_post_uri( $product_id );
269 Permalink_Manager_URI_Functions_Post::save_uri( $product_id, $new_uri, $is_new_post );
270 }
271 }
272
273 /**
274 * Check the current request is a WooCommerce AJAX request. If it is, check the translated page's URL should be returned
275 *
276 * @param string $permalink The full URL of the post
277 * @param WP_Post $post The post object
278 * @param string $old_permalink The original URL of the post.
279 *
280 * @return string The permalink is being returned.
281 */
282 function woocommerce_translate_ajax_fragments_urls( $permalink, $post, $old_permalink ) {
283 // Use it only if the permalinks are different
284 if ( $permalink == $old_permalink || $post->post_type !== 'page' ) {
285 return $permalink;
286 }
287
288 // phpcs:disable WordPress.Security.NonceVerification.Recommended
289 // A. Native WooCommerce AJAX events
290 if ( ! empty( $_REQUEST['wc-ajax'] ) ) {
291 $action = sanitize_title( wp_unslash( $_REQUEST['wc-ajax'] ) );
292 } // B. Shoptimizer theme
293 else if ( ! empty( $_REQUEST['action'] ) ) {
294 $action = sanitize_title( wp_unslash( $_REQUEST['action'] ) );
295 }
296 // phpcs:enable WordPress.Security.NonceVerification.Recommended
297
298 // Allowed action names
299 $allowed_actions = array( 'shoptimizer_pdp_ajax_atc', 'get_refreshed_fragments' );
300
301 if ( ! empty( $action ) && in_array( $action, $allowed_actions ) ) {
302 $translated_post_id = apply_filters( 'wpml_object_id', $post->ID, 'page' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
303 $permalink = ( $translated_post_id !== $post->ID ) ? get_permalink( $translated_post_id ) : $permalink;
304 }
305
306 return $permalink;
307 }
308
309 /**
310 * 4FA. Add a new column to the WooCommerce CSV Import/Export tool
311 *
312 * @param array $columns The array of columns to be displayed.
313 *
314 * @return array The $columns array.
315 */
316 function woocommerce_csv_custom_uri_column( $columns ) {
317 if ( ! is_array( $columns ) ) {
318 return $columns;
319 }
320
321 $label = __( 'Custom URI', 'permalink-manager' );
322 $key = 'custom_uri';
323
324 if ( current_filter() == 'woocommerce_csv_product_import_mapping_default_columns' ) {
325 $columns[ $label ] = $key;
326 } else {
327 $columns[ $key ] = $label;
328 }
329
330 return $columns;
331 }
332
333 /**
334 * 4FB. Return the custom permalink of the product if it exists, otherwise return the default URI
335 *
336 * @param string $value The value of the column.
337 * @param WC_Product $product The product object.
338 * @param mixed $column_id The column ID.
339 *
340 * @return string The custom permalink or default permalink
341 */
342 function woocommerce_export_custom_uri_value( $value, $product, $column_id ) {
343 if ( empty( $value ) && ! empty( $product ) ) {
344 $product_id = $product->get_id();
345
346 // Get custom permalink or default permalink
347 $value = Permalink_Manager_URI_Functions_Post::get_post_uri( $product_id );
348 }
349
350 return $value;
351 }
352
353 /**
354 * 4FC. Set the custom URI for the product using the value from CSV file, if not set use the default permalink
355 *
356 * @param WC_Product $product The product object.
357 * @param array $data The data array for the current row being imported.
358 */
359 function woocommerce_csv_import_custom_uri( $product, $data ) {
360 if ( ! empty( $product ) ) {
361 $product_id = $product->get_id();
362
363 // Ignore variations
364 if ( $product->get_type() == 'variation' ) {
365 return;
366 }
367
368 $current_uri = Permalink_Manager_URI_Functions::get_single_uri( $product_id, false, true );
369
370 // A. Use default permalink if "Custom URI" is not set and did not exist before
371 if ( empty( $current_uri ) && empty( $data['custom_uri'] ) ) {
372 $custom_uri = Permalink_Manager_URI_Functions_Post::get_default_post_uri( $product_id, false, true );
373 } else if ( ! empty( $data['custom_uri'] ) ) {
374 $custom_uri = Permalink_Manager_Helper_Functions::sanitize_title( $data['custom_uri'] );
375 } else {
376 return;
377 }
378
379 Permalink_Manager_URI_Functions::save_single_uri( $product_id, $custom_uri, false, true );
380 }
381 }
382
383 /**
384 * Declare support for 'High-Performance order storage (COT)' and other features in WooCommerce
385 */
386 function woocommerce_declare_compatibility() {
387 $features_util_class = '\Automattic\WooCommerce\Utilities\FeaturesUtil';
388
389 if ( class_exists( $features_util_class ) && method_exists( $features_util_class, 'declare_compatibility' ) ) {
390 $features = method_exists( $features_util_class, 'get_features' ) ? $features_util_class::get_features( true ) : array();
391
392 foreach ( array_keys( $features ) as $feature ) {
393 $features_util_class::declare_compatibility( $feature, PERMALINK_MANAGER_BASENAME );
394 }
395 }
396 }
397
398 /**
399 * Extract the Wishlist ID from the URI and add it to the $uri_parts array (WooCommerce Wishlist Plugin)
400 *
401 * @param array $uri_parts An array of the URI parts.
402 * @param string $request_url The URL that was requested.
403 * @param array $endpoints An array of all the endpoints that are currently registered.
404 *
405 * @return array The URI parts.
406 */
407 function ti_woocommerce_wishlist_uris( $uri_parts, $request_url, $endpoints ) {
408 $wishlist_pid = function_exists( 'tinv_get_option' ) ? tinv_get_option( 'general', 'page_wishlist' ) : '';
409
410 // Find the Wishlist page URI
411 if ( is_numeric( $wishlist_pid ) ) {
412 $current_uri = Permalink_Manager_URI_Functions::get_single_uri( $wishlist_pid, false, true );
413
414 if ( ! empty( $current_uri ) ) {
415 $wishlist_uri = preg_quote( $current_uri, '/' );
416
417 // Extract the Wishlist ID
418 preg_match( "/^({$wishlist_uri})\/([^\/]+)\/?$/", $uri_parts['uri'], $output_array );
419
420 if ( ! empty( $output_array[2] ) ) {
421 $uri_parts['uri'] = $output_array[1];
422 $uri_parts['endpoint'] = 'tinvwlID';
423 $uri_parts['endpoint_value'] = $output_array[2];
424 }
425 }
426 }
427
428 return $uri_parts;
429 }
430
431 /**
432 * Keep the query strings appended to the product permalinks by WooCommerce Subscriptions
433 *
434 * @param string $permalink
435 * @param WP_Post $post
436 * @param string $old_permalink
437 *
438 * @return string
439 */
440 function wcs_fix_subscription_links( $permalink, $post, $old_permalink ) {
441 if ( ! empty( $post->post_type ) && $post->post_type == 'product' && strpos( $old_permalink, 'switch-subscription=' ) !== false ) {
442 $query_string = wp_parse_url( $old_permalink, PHP_URL_QUERY );
443
444 if ( ! empty( $query_string ) ) {
445 wp_parse_str( $query_string, $parsed_args );
446 $permalink = add_query_arg( $parsed_args, $permalink );
447 }
448 }
449
450 return $permalink;
451 }
452 }