PluginProbe ʕ •ᴥ•ʔ
Permalink Manager Lite / 2.6.0
Permalink Manager Lite v2.6.0
2.6.0 2.5.5 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 / core / permalink-manager-actions.php
permalink-manager / includes / core Last commit date
permalink-manager-actions.php 2 weeks ago permalink-manager-admin-functions.php 2 weeks ago permalink-manager-core-functions.php 2 weeks ago permalink-manager-debug.php 2 weeks ago permalink-manager-gutenberg.php 2 weeks ago permalink-manager-helper-functions.php 2 weeks ago permalink-manager-permastructures-functions.php 2 weeks ago permalink-manager-uri-functions-post.php 2 weeks ago permalink-manager-uri-functions.php 2 weeks ago
permalink-manager-actions.php
790 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 /**
8 * Additional hooks for "Permalink Manager Pro"
9 */
10 class Permalink_Manager_Actions {
11
12 public function __construct() {
13 add_action( 'admin_init', array( $this, 'trigger_action' ), 9 );
14 add_action( 'admin_init', array( $this, 'extra_actions' ) );
15
16 // Ajax-based functions
17 if ( is_admin() ) {
18 add_action( 'wp_ajax_pm_bulk_tools', array( $this, 'ajax_bulk_tools' ) );
19 add_action( 'wp_ajax_pm_save_permalink', array( $this, 'ajax_save_permalink' ) );
20 add_action( 'wp_ajax_pm_detect_duplicates', array( $this, 'ajax_detect_duplicates' ) );
21 add_action( 'wp_ajax_pm_dismissed_notice_handler', array( $this, 'ajax_hide_global_notice' ) );
22 }
23 }
24
25 /**
26 * Route the requests to functions that save datasets with associated callbacks
27 */
28 public function trigger_action() {
29 global $permalink_manager_after_sections_html;
30
31 // 1. Check if the form was submitted
32 if ( empty( $_POST ) ) {
33 return;
34 }
35
36 // 2. Do nothing if search query is not empty
37 if ( isset( $_POST['uri_editor_nonce'] ) && ( isset( $_REQUEST['search-submit'] ) || isset( $_REQUEST['filter-button'] ) ) ) {
38 $this->trigger_filter_action();
39 }
40
41 $actions_map = array(
42 'uri_editor_nonce' => array( 'function' => 'update_all_permalinks', 'display_uri_table' => true ),
43 'permalink_manager_options' => array( 'function' => 'save_settings' ),
44 'permalink_manager_permastructs' => array( 'function' => 'save_permastructures' ),
45 'import' => array( 'function' => 'import_custom_permalinks_uris' ),
46 );
47
48 // 3. Find the action
49 foreach ( $actions_map as $action => $map ) {
50 $nonce = ( isset( $_POST[ $action ] ) ) ? sanitize_key( $_POST[ $action ] ) : '';
51
52 if ( ! empty( $nonce ) && wp_verify_nonce( $nonce, 'permalink-manager' ) ) {
53 // Execute the function
54 $output = call_user_func( array( $this, $map['function'] ) );
55
56 // Get list of updated URIs
57 if ( ! empty( $map['display_uri_table'] ) ) {
58 $updated_slugs_count = ( isset( $output['updated_count'] ) && $output['updated_count'] > 0 ) ? $output['updated_count'] : false;
59 $updated_slugs_array = ( $updated_slugs_count ) ? $output['updated'] : '';
60 }
61
62 // Trigger only one function
63 break;
64 }
65 }
66
67 // 4. Display the slugs table (and append the globals)
68 if ( isset( $updated_slugs_count ) && isset( $updated_slugs_array ) ) {
69 $permalink_manager_after_sections_html .= Permalink_Manager_UI_Elements::display_updated_slugs( $updated_slugs_array );
70 }
71 }
72
73 /**
74 * Adjust the Bulk URI Editor filter URL
75 *
76 * @return void
77 */
78 public function trigger_filter_action() {
79 if ( empty( $_POST['uri_editor_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['uri_editor_nonce'] ), 'permalink-manager' ) ) {
80 return;
81 }
82
83 $query_args = array(
84 'langcode' => ! empty( $_REQUEST['langcode'] ) ? sanitize_key( $_REQUEST['langcode'] ) : null,
85 'month' => ! empty( $_REQUEST['month'] ) ? sanitize_key( $_REQUEST['month'] ) : null,
86 's' => ! empty( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : null,
87 );
88
89 if ( ! empty( $query_args ) ) {
90 $sendback = remove_query_arg( array_keys( $query_args ), wp_get_referer() );
91 $sendback = add_query_arg( array_filter( $query_args ), $sendback );
92
93 wp_safe_redirect( $sendback );
94 exit;
95 }
96 }
97
98 /**
99 * Route the requests to the additional tools-related functions with the relevant callbacks
100 */
101 public static function extra_actions() {
102 global $permalink_manager_before_sections_html;
103
104 if ( current_user_can( 'manage_options' ) && ! empty( $_GET['permalink-manager-nonce'] ) ) {
105 // Check if the nonce field is correct
106 $nonce = sanitize_key( $_GET['permalink-manager-nonce'] );
107
108 if ( ! wp_verify_nonce( $nonce, 'permalink-manager' ) ) {
109 $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' );
110
111 return;
112 }
113
114 if ( isset( $_GET['clear-permalink-manager-uris'] ) ) {
115 self::clear_all_uris();
116 } else if ( isset( $_GET['remove-permalink-manager-settings'] ) ) {
117 $option_name = sanitize_key( $_GET['remove-permalink-manager-settings'] );
118 self::remove_plugin_data( $option_name );
119 } else if ( ! empty( $_REQUEST['remove-uri'] ) ) {
120 $uri_key = sanitize_key( $_REQUEST['remove-uri'] );
121 self::force_clear_single_element_uris_and_redirects( $uri_key );
122 } else if ( ! empty( $_REQUEST['remove-redirect'] ) ) {
123 $redirect_key = sanitize_key( $_REQUEST['remove-redirect'] );
124 self::force_clear_single_redirect( $redirect_key );
125 }
126 } else if ( ! empty( $_POST['screen-options-apply'] ) ) {
127 self::save_screen_options();
128 }
129 }
130
131 /**
132 * Bulk remove obsolete custom permalinks and redirects
133 */
134 public static function clear_all_uris() {
135 global $permalink_manager_redirects, $permalink_manager_before_sections_html;
136
137 // Get all custom permalinks & redirects
138 $custom_permalinks = Permalink_Manager_URI_Functions::get_all_uris();
139 $custom_redirects = (array) $permalink_manager_redirects;
140
141 // Check if array with custom URIs exists
142 if ( empty( $custom_permalinks ) ) {
143 return;
144 }
145
146 // Count removed URIs & redirects
147 $removed_uris = 0;
148 $removed_redirects = 0;
149
150 // Get all element IDs
151 $element_ids = array_merge( array_keys( $custom_permalinks ), array_keys( $custom_redirects ) );
152
153 // 1. Remove unused custom URI & redirects for deleted post or term
154 foreach ( $element_ids as $element_id ) {
155 $count = self::clear_single_element_uris_and_redirects( $element_id, true );
156
157 $removed_uris = ( ! empty( $count[0] ) ) ? $count[0] + $removed_uris : $removed_uris;
158 $removed_redirects = ( ! empty( $count[1] ) ) ? $count[1] + $removed_redirects : $removed_redirects;
159 }
160
161 // 2. Keep only a single redirect
162 $removed_redirects += self::clear_redirects_array();
163
164 // 3. Save cleared URIs & Redirects
165 if ( $removed_uris > 0 || $removed_redirects > 0 ) {
166 Permalink_Manager_URI_Functions::save_all_uris();
167 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
168
169 // Translators: 1: Number of custom URIs, 2: Number of custom redirects.
170 $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' );
171 } else {
172 $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' );
173 }
174 }
175
176 /**
177 * Remove obsolete custom permalink & redirects for specific post or term
178 *
179 * @param string|int $element_id
180 * @param bool $count_removed
181 *
182 * @return array
183 */
184 public static function clear_single_element_uris_and_redirects( $element_id, $count_removed = false ) {
185 global $wpdb, $permalink_manager_redirects, $permalink_manager_options;
186
187 // Count removed URIs & redirects
188 $removed_uris = 0;
189 $removed_redirects = 0;
190
191 // Only admin users can remove the broken URIs for removed post types & taxonomies
192 $check_if_admin = is_admin();
193
194 // Check if the advanced mode is turned on
195 $advanced_mode = Permalink_Manager_Helper_Functions::is_advanced_mode_on();
196
197 // If "Disable URI Editor to disallow Permalink changes" is set globally, the pages that follow the global settings should also be removed
198 if ( $advanced_mode && ! empty( $permalink_manager_options["general"]["auto_update_uris"] ) && $permalink_manager_options["general"]["auto_update_uris"] == 2 ) {
199 $strict_mode = true;
200 } else {
201 $strict_mode = false;
202 }
203
204 // 1. Check if element exists
205 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct SQL query is needed to join multiple tables
206 if ( strpos( $element_id, 'tax-' ) !== false ) {
207 $term_id = preg_replace( "/[^0-9]/", "", $element_id );
208 $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 ) );
209
210 // 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
211 $remove = ( ! empty( $term_info->taxonomy ) ) ? Permalink_Manager_Helper_Functions::is_taxonomy_disabled( $term_info->taxonomy, $check_if_admin ) : true;
212
213 // Remove custom URIs for URIs disabled in URI Editor
214 if ( $strict_mode ) {
215 $remove = ( empty( $term_info->meta_value ) || $term_info->meta_value == 2 ) ? true : $remove;
216 } else {
217 $remove = ( ! empty( $term_info->meta_value ) && $term_info->meta_value == 2 ) ? true : $remove;
218 }
219 } else if ( is_numeric( $element_id ) ) {
220 $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 ) );
221
222 // 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
223 $remove = ( ! empty( $post_info->post_type ) ) ? Permalink_Manager_Helper_Functions::is_post_type_disabled( $post_info->post_type, $check_if_admin ) : true;
224
225 // Remove custom URIs for URIs disabled in URI Editor
226 if ( $strict_mode ) {
227 $remove = ( empty( $post_info->meta_value ) || $post_info->meta_value == 2 ) ? true : $remove;
228 } else {
229 $remove = ( ! empty( $post_info->meta_value ) && $post_info->meta_value == 2 ) ? true : $remove;
230 }
231
232 // Remove custom URIs for attachments redirected with Yoast's SEO Premium
233 $yoast_permalink_options = ( class_exists( 'WPSEO_Premium' ) ) ? get_option( 'wpseo_permalinks' ) : array();
234
235 if ( ! empty( $yoast_permalink_options['redirectattachment'] ) && $post_info->post_type == 'attachment' ) {
236 $attachment_parent = $wpdb->get_var( $wpdb->prepare( "SELECT post_parent FROM {$wpdb->prefix}posts WHERE ID = %d AND post_type = %s", $element_id, 'attachment' ) );
237 if ( ! empty( $attachment_parent ) ) {
238 $remove = true;
239 }
240 }
241 }
242 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
243
244 // 2A. Remove ALL unused custom permalinks & redirects
245 if ( ! empty( $remove ) ) {
246 $current_uri = Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null );
247
248 // Remove URI
249 if ( ! empty( $current_uri ) ) {
250 $removed_uris = 1;
251 Permalink_Manager_URI_Functions::remove_single_uri( $element_id, null, false );
252 }
253
254 // Remove all custom redirects
255 if ( ! empty( $permalink_manager_redirects[ $element_id ] ) && is_array( $permalink_manager_redirects[ $element_id ] ) ) {
256 $removed_redirects = count( $permalink_manager_redirects[ $element_id ] );
257 unset( $permalink_manager_redirects[ $element_id ] );
258 }
259 } // 2B. Check if the post/term uses the same URI for both permalink & custom redirects
260 else {
261 $removed_redirect = self::clear_single_element_duplicated_redirect( $element_id, false );
262 $removed_redirects = ( ! empty( $removed_redirect ) ) ? 1 : 0;
263 }
264
265 // Check if function should only return the counts or update
266 if ( $count_removed ) {
267 return array( $removed_uris, $removed_redirects );
268 } else if ( ! empty( $removed_uris ) || ! empty( $removed_redirects ) ) {
269 Permalink_Manager_URI_Functions::save_all_uris();
270 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
271 }
272
273 return array();
274 }
275
276 /**
277 * Remove the duplicated custom redirect if the post/term has the same URI for both custom permalink and custom redirect
278 *
279 * @param string|int $element_id
280 * @param bool $save_redirects
281 * @param string $uri
282 *
283 * @return int
284 */
285 public static function clear_single_element_duplicated_redirect( $element_id, $save_redirects = true, $uri = null ) {
286 global $permalink_manager_redirects;
287
288 // If the custom permalink is not changed ($uri) use the one that is currently used
289 if ( ! empty( $uri ) ) {
290 $current_uri = $uri;
291 } else {
292 $current_uri = Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null );
293 }
294
295 if ( ! empty( $current_uri ) && ! empty( $permalink_manager_redirects[ $element_id ] ) && in_array( $current_uri, $permalink_manager_redirects[ $element_id ] ) ) {
296 $duplicated_redirect_id = array_search( $current_uri, $permalink_manager_redirects[ $element_id ] );
297 unset( $permalink_manager_redirects[ $element_id ][ $duplicated_redirect_id ] );
298 }
299
300 // Update the redirects array in the database if the duplicated redirect was unset
301 if ( isset( $duplicated_redirect_id ) && $save_redirects ) {
302 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
303 }
304
305 return ( isset( $duplicated_redirect_id ) ) ? 1 : 0;
306 }
307
308 /**
309 * Remove the duplicated if the same URI is used for multiple custom redirects and return the removed redirects count
310 *
311 * @param bool $save_redirects
312 *
313 * @return int
314 */
315 public static function clear_redirects_array( $save_redirects = false ) {
316 global $permalink_manager_redirects;
317
318 $removed_redirects = 0;
319
320 $all_redirect_duplicates = Permalink_Manager_Admin_Functions::get_all_duplicates();
321
322 foreach ( $all_redirect_duplicates as $single_redirect_duplicate ) {
323 $last_element = reset( $single_redirect_duplicate );
324
325 foreach ( $single_redirect_duplicate as $redirect_key ) {
326 // Keep a single redirect
327 if ( $last_element == $redirect_key ) {
328 continue;
329 }
330 preg_match( "/redirect-(\d+)_(tax-\d+|\d+)/", $redirect_key, $ids );
331
332 if ( ! empty( $ids[2] ) && ! empty( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] ) ) {
333 $removed_redirects ++;
334 unset( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] );
335 }
336 }
337 }
338
339 // Update the redirects array in the database if the duplicated redirect was unset
340 if ( isset( $duplicated_redirect_id ) && $save_redirects ) {
341 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
342 }
343
344 return $removed_redirects;
345 }
346
347 /**
348 * Remove custom permalinks & custom redirects for requested post or term
349 *
350 * @param $uri_key
351 *
352 * @return bool
353 */
354 public static function force_clear_single_element_uris_and_redirects( $uri_key ) {
355 global $permalink_manager_redirects, $permalink_manager_before_sections_html;
356
357 $custom_uri = Permalink_Manager_URI_Functions::get_single_uri( $uri_key, false, true, null );
358
359 // Check if custom URI is set
360 if ( ! empty( $custom_uri ) ) {
361 Permalink_Manager_URI_Functions::remove_single_uri( $uri_key, null, true );
362 // translators: %s is the custom URI that was removed.
363 $updated = Permalink_Manager_UI_Elements::get_alert_message( sprintf( __( 'URI "%s" was removed successfully!', 'permalink-manager' ), $custom_uri ), 'updated' );
364 }
365
366 // Check if custom redirects are set
367 if ( isset( $permalink_manager_redirects[ $uri_key ] ) ) {
368 unset( $permalink_manager_redirects[ $uri_key ] );
369 update_option( 'permalink-manager-redirects', $permalink_manager_redirects );
370 }
371
372 if ( empty( $updated ) ) {
373 $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' );
374 } else {
375 // Display the alert in admin panel
376 if ( isset( $permalink_manager_before_sections_html ) && is_admin() ) {
377 $permalink_manager_before_sections_html .= $updated;
378 }
379 }
380
381 return true;
382 }
383
384 /**
385 * Remove only custom redirects for requested post or term
386 *
387 * @param string $redirect_key
388 */
389 public static function force_clear_single_redirect( $redirect_key ) {
390 global $permalink_manager_redirects, $permalink_manager_before_sections_html;
391
392 preg_match( "/redirect-(\d+)_(tax-\d+|\d+)/", $redirect_key, $ids );
393
394 if ( ! empty( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] ) ) {
395 unset( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] );
396
397 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
398
399 $permalink_manager_before_sections_html = Permalink_Manager_UI_Elements::get_alert_message( __( 'The redirect was removed successfully!', 'permalink-manager' ), 'updated' );
400 }
401 }
402
403 /**
404 * Save "Screen Options"
405 */
406 public static function save_screen_options() {
407 check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
408
409 $screen_options = ( isset( $_POST['screen-options'] ) ) ? map_deep( wp_unslash( $_POST['screen-options'] ), 'sanitize_text_field' ) : array();
410
411 if ( ! empty( $screen_options ) ) {
412 self::save_settings( 'screen-options', $screen_options );
413 }
414 }
415
416 /**
417 * Save the plugin settings
418 *
419 * @param bool $field
420 * @param bool $value
421 * @param bool $display_alert
422 */
423 public static function save_settings( $field = false, $value = false, $display_alert = true ) {
424 global $permalink_manager_options, $permalink_manager_before_sections_html;
425
426 // Info: The settings array is used also by "Screen Options"
427 $new_options = $permalink_manager_options;
428
429 // Save only selected field/sections
430 if ( $field && $value ) {
431 $new_options[ $field ] = $value;
432 } else {
433 $post_fields = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated in enclosing function
434
435 foreach ( $post_fields as $option_name => $option_value ) {
436 $new_options[ $option_name ] = $option_value;
437 }
438 }
439
440 // Allow only white-listed option groups
441 foreach ( $new_options as $group => $group_options ) {
442 if ( ! in_array( $group, array( 'licence', 'screen-options', 'general', 'permastructure-settings', 'stop-words' ) ) ) {
443 unset( $new_options[ $group ] );
444 }
445 }
446
447 // Sanitize & override the global with new settings
448 $new_options = Permalink_Manager_Helper_Functions::sanitize_array( $new_options );
449 $permalink_manager_options = $new_options = array_filter( $new_options );
450
451 // Save the settings in database
452 update_option( 'permalink-manager', $new_options );
453
454 // Display the message
455 $permalink_manager_before_sections_html .= ( $display_alert ) ? Permalink_Manager_UI_Elements::get_alert_message( __( 'The settings are saved!', 'permalink-manager' ), 'updated' ) : "";
456 }
457
458 /**
459 * Save the permastructures
460 */
461 public static function save_permastructures() {
462 $permastructure_options = $permastructures = array();
463 $permastructure_types = array( 'post_types', 'taxonomies' );
464
465 // Split permastructures & sanitize them
466 // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- The nonce is already validated in enclosing function
467 foreach ( $permastructure_types as $type ) {
468 if ( empty( $_POST[ $type ] ) || ! is_array( $_POST[ $type ] ) ) {
469 continue;
470 }
471
472 $permastructures[ $type ] = $_POST[ $type ];
473
474 foreach ( $permastructures[ $type ] as &$single_permastructure ) {
475 $single_permastructure = Permalink_Manager_Helper_Functions::sanitize_title( $single_permastructure, true, false, false );
476 $single_permastructure = trim( $single_permastructure, '\/ ' );
477 }
478 }
479
480 if ( ! empty( $_POST['permastructure-settings'] ) ) {
481 $permastructure_options = $_POST['permastructure-settings'];
482 }
483 // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
484
485 // A. Permastructures
486 if ( ! empty( $permastructures['post_types'] ) || ! empty( $permastructures['taxonomies'] ) ) {
487 // Save the settings in a database
488 Permalink_Manager_Permastructure_Functions::save_all_permastructures( $permastructures );
489 }
490
491 // B. Permastructure settings
492 if ( ! empty( $permastructure_options ) ) {
493 self::save_settings( 'permastructure-settings', $permastructure_options );
494 }
495 }
496
497 /**
498 * Update all permalinks in "Bulk URI Editor"
499 */
500 function update_all_permalinks() {
501 // Check if posts or terms should be updated
502 if ( ! empty( $_POST['content_type'] ) && $_POST['content_type'] == 'taxonomies' ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated in enclosing function
503 return Permalink_Manager_URI_Functions_Tax::update_all_permalinks();
504 } else {
505 return Permalink_Manager_URI_Functions_Post::update_all_permalinks();
506 }
507 }
508
509 /**
510 * Remove a specific section of the plugin data stored in the database
511 *
512 * @param $field_name
513 */
514 public static function remove_plugin_data( $field_name ) {
515 global $permalink_manager, $permalink_manager_before_sections_html;
516
517 // Make sure that the user is allowed to remove the plugin data
518 if ( ! current_user_can( 'manage_options' ) ) {
519 $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' );
520 }
521
522 switch ( $field_name ) {
523 case 'uris' :
524 $option_name = 'permalink-manager-uris';
525 $alert = __( 'Custom permalinks', 'permalink-manager' );
526 break;
527 case 'redirects' :
528 $option_name = 'permalink-manager-redirects';
529 $alert = __( 'Custom redirects', 'permalink-manager' );
530 break;
531 case 'external-redirects' :
532 $option_name = 'permalink-manager-external-redirects';
533 $alert = __( 'External redirects', 'permalink-manager' );
534 break;
535 case 'permastructs' :
536 $option_name = 'permalink-manager-permastructs';
537 $alert = __( 'Permastructure settings', 'permalink-manager' );
538 break;
539 case 'settings' :
540 $option_name = 'permalink-manager';
541 $alert = __( 'Permastructure settings', 'permalink-manager' );
542 break;
543 default :
544 $alert = '';
545 }
546
547 if ( ! empty( $option_name ) ) {
548 // Remove the option from DB
549 delete_option( $option_name );
550
551 // Reload globals
552 $permalink_manager->get_options_and_globals();
553
554 // Translators: %s is the name of the data field that was removed
555 $alert_message = sprintf( __( '%s were removed!', 'permalink-manager' ), $alert );
556 $permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( $alert_message, 'updated updated_slugs' );
557 }
558 }
559
560 /**
561 * Trigger bulk tools ("Regenerate & reset", "Find & replace") via AJAX
562 */
563 function ajax_bulk_tools() {
564 global $sitepress, $wpdb;
565
566 // Define variables
567 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No items</strong> were processed!', 'permalink-manager' ), 'error updated_slugs' ) );
568
569 // Get the name of the function
570 if ( isset( $_POST['regenerate'] ) ) {
571 $nonce_name = sanitize_key( $_POST['regenerate'] );
572 $operation = 'regenerate';
573 } else if ( isset( $_POST['find_and_replace'] ) ) {
574 $nonce_name = sanitize_key( $_POST['find_and_replace'] );
575 $operation = ( ! empty( $_POST['old_string'] ) && ! empty( $_POST['new_string'] ) ) ? 'find_and_replace' : '';
576 }
577
578 // Validate the nonce
579 if ( empty( $nonce_name ) || ! wp_verify_nonce( $nonce_name, 'permalink-manager' ) ) {
580 $error = true;
581 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( 'Nonce is invalid!', 'permalink-manager' ), 'error updated_slugs' ) );
582 }
583
584 // Check if the user can edit the custom permalinks
585 if ( ! current_user_can( 'manage_options' ) ) {
586 $error = true;
587 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( 'You are not allowed to perform this action.', 'permalink-manager' ), 'error updated_slugs' ) );
588 }
589
590 // Get the session ID
591 $uniq_id = ( ! empty( $_POST['pm_session_id'] ) ) ? sanitize_key( $_POST['pm_session_id'] ) : '';
592
593 // Get content type & post statuses
594 if ( ! empty( $_POST['content_type'] ) && $_POST['content_type'] == 'taxonomies' ) {
595 $content_type = 'taxonomies';
596
597 if ( empty( $_POST['taxonomies'] ) ) {
598 $error = true;
599 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No taxonomy</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
600 }
601 } else {
602 $content_type = 'post_types';
603
604 // Check if any post type was selected
605 if ( empty( $_POST['post_types'] ) ) {
606 $error = true;
607 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No post type</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
608 }
609
610 // Check post status
611 if ( empty( $_POST['post_statuses'] ) ) {
612 $error = true;
613 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No post status</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
614 }
615 }
616
617 if ( ! empty( $operation ) && empty( $error ) ) {
618 // Hotfix for WPML (start)
619 if ( $sitepress ) {
620 remove_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10 );
621 remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1 );
622 remove_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10 );
623 remove_filter( 'get_pages', array( $sitepress, 'get_pages_adjust_ids' ), 1 );
624 }
625
626 // Get the mode
627 $mode = ( isset( $_POST['mode'] ) ) ? sanitize_key( $_POST['mode'] ) : 'custom_uris';
628 $preview_mode = ( ! empty( $_POST['preview_mode'] ) ) ? true : false;
629
630 // Get items (try to get them from transient)
631 $items = get_transient( "pm_{$uniq_id}" );
632
633 // Get the iteration count and chunk size
634 $iteration = isset( $_POST['iteration'] ) ? intval( $_POST['iteration'] ) : 1;
635 $chunk_size = apply_filters( 'permalink_manager_chunk_size', 50 );
636
637 if ( empty( $items ) && ! empty ( $chunk_size ) ) {
638 if ( $content_type == 'taxonomies' ) {
639 $items = Permalink_Manager_URI_Functions_Tax::get_items();
640 } else {
641 $items = Permalink_Manager_URI_Functions_Post::get_items();
642 }
643
644 if ( ! empty( $items ) ) {
645 // Count how many items need to be processed
646 $total = count( $items );
647
648 // Split items array into chunks and save them to transient
649 $items = array_chunk( $items, $chunk_size );
650
651 set_transient( "pm_{$uniq_id}", $items, 600 );
652
653 // Check for MySQL errors
654 if ( ! empty( $wpdb->last_error ) ) {
655 printf( '%s (%sMB)', esc_html( $wpdb->last_error ), esc_html( number_format( strlen( serialize( $items ) ) / 1000000, 2 ) ) );
656 http_response_code( 500 );
657 die();
658 }
659 }
660 }
661
662 // Get homepage URL and ensure that it ends with slash
663 $home_url = Permalink_Manager_Permastructure_Functions::get_permalink_base() . "/";
664
665 // Process the variables from $_POST object
666 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- The escaped slashes can be a part of REGEX formula
667 $old_string = ( ! empty( $_POST['old_string'] ) ) ? str_replace( $home_url, '', esc_sql( sanitize_text_field( $_POST['old_string'] ) ) ) : '';
668 $new_string = ( ! empty( $_POST['new_string'] ) ) ? str_replace( $home_url, '', esc_sql( sanitize_text_field( $_POST['new_string'] ) ) ) : '';
669 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
670
671 // Process only one subarray
672 if ( ! empty( $items[ $iteration - 1 ] ) ) {
673 $chunk = $items[ $iteration - 1 ];
674
675 // Check how many iterations are needed
676 $total_iterations = count( $items );
677
678 if ( $content_type == 'taxonomies' ) {
679 $output = Permalink_Manager_URI_Functions_Tax::bulk_process_items( $chunk, $mode, $operation, $old_string, $new_string, $preview_mode );
680 } else {
681 $output = Permalink_Manager_URI_Functions_Post::bulk_process_items( $chunk, $mode, $operation, $old_string, $new_string, $preview_mode );
682 }
683
684 if ( ! empty( $output['updated_count'] ) ) {
685 $return = array_merge( $return, (array) Permalink_Manager_UI_Elements::display_updated_slugs( $output['updated'], true, true, $preview_mode ) );
686 $return['updated_count'] = $output['updated_count'];
687 }
688
689 // Send total number of processed items with a first chunk
690 if ( ! empty( $total ) && ! empty( $total_iterations ) && $iteration == 1 ) {
691 $return['total'] = $total;
692 $return['items'] = $items;
693 }
694
695 $return['iteration'] = $iteration;
696 $return['total_iterations'] = $total_iterations;
697 $return['progress'] = $chunk_size * $iteration;
698 $return['chunk'] = $chunk;
699
700 // After all chunks are processed remove the transient
701 if ( $iteration == $total_iterations ) {
702 delete_transient( "pm_{$uniq_id}" );
703 }
704 }
705
706 // Hotfix for WPML (end)
707 if ( $sitepress ) {
708 add_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10, 4 );
709 add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
710 add_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10, 2 );
711 add_filter( 'get_pages', array( $sitepress, 'get_pages_adjust_ids' ), 1, 2 );
712 }
713 }
714
715 wp_send_json( $return );
716 die();
717 }
718
719 /**
720 * Save permalink via AJAX
721 */
722 public function ajax_save_permalink() {
723 $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
724
725 if ( ! empty( $element_id ) && is_numeric( $element_id ) && current_user_can( 'edit_post', $element_id ) ) {
726 Permalink_Manager_URI_Functions_Post::update_post_hook( $element_id );
727
728 // Reload URI Editor & clean post cache
729 clean_post_cache( $element_id );
730 die();
731 }
732 }
733
734 /**
735 * Check if URI was used before
736 */
737 function ajax_detect_duplicates() {
738 if ( ! Permalink_Manager_Admin_Functions::current_user_can_edit_uris() ) {
739 wp_send_json_error( array( 'message' => __( 'You are not allowed to perform this action.', 'permalink-manager' ) ), 403 );
740 }
741
742 $duplicate_alert = __( 'Permalink is already in use, please select another one!', 'permalink-manager' );
743 $duplicates_data = array();
744
745 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- No data is saved here; the array is recursively sanitized via sanitize_array().
746 $custom_uris = ( ! empty( $_REQUEST['custom_uris'] ) ) ? Permalink_Manager_Helper_Functions::sanitize_array( wp_unslash( $_REQUEST['custom_uris'] ) ) : array();
747
748 if ( ! empty( $custom_uris ) ) {
749 foreach ( $custom_uris as $raw_element_id => $element_uri ) {
750 $element_id = sanitize_key( $raw_element_id );
751 $duplicates_data[ $element_id ] = Permalink_Manager_URI_Functions::is_uri_duplicated( $element_uri, $element_id ) ? $duplicate_alert : 0;
752 }
753 } else {
754 // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- No data is saved here
755 $single_uri = ( ! empty( $_REQUEST['custom_uri'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['custom_uri'] ) ) : '';
756 $element_id = ( ! empty( $_REQUEST['element_id'] ) ) ? sanitize_key( $_REQUEST['element_id'] ) : '';
757 // phpcs:enable
758
759 if ( ! empty( $single_uri ) && ! empty( $element_id ) ) {
760 $duplicates_data = Permalink_Manager_URI_Functions::is_uri_duplicated( $single_uri, $element_id );
761 }
762 }
763
764 wp_send_json( $duplicates_data );
765 }
766
767 /**
768 * Hide global notices (AJAX)
769 */
770 function ajax_hide_global_notice() {
771 global $permalink_manager_alerts;
772
773 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Low-risk cosmetic notice dismissal; authenticated admin-only AJAX action and no nonce is transmitted by the notice markup.
774 $alert_id = ( ! empty( $_REQUEST['alert_id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['alert_id'] ) ) : "";
775 if ( ! empty( $permalink_manager_alerts[ $alert_id ] ) ) {
776 $dismissed_transient_name = sprintf( 'permalink-manager-notice_%s', $alert_id );
777 $dismissed_time = ( ! empty( $permalink_manager_alerts[ $alert_id ]['dismissed_time'] ) ) ? (int) $permalink_manager_alerts[ $alert_id ]['dismissed_time'] : DAY_IN_SECONDS;
778
779 set_transient( $dismissed_transient_name, 1, $dismissed_time );
780 }
781 }
782
783 /**
784 * Import old URIs from "Custom Permalinks" (Pro)
785 */
786 function import_custom_permalinks_uris() {
787 Permalink_Manager_Third_Parties::import_custom_permalinks_uris();
788 }
789
790 }